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

View File

@ -16,13 +16,34 @@
# You should have received a copy of the GNU Affero General Public License
# along with Axuy. If not, see <https://www.gnu.org/licenses/>.
from itertools import chain, combinations_with_replacement, permutations
import numpy
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."""
return numpy.float32([i / 255 for i in bytes.fromhex(hex_color)])
def color(name):
"""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):

View File

@ -16,41 +16,86 @@
# You should have received a copy of the GNU Affero General Public License
# 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 numpy as np
from pyrr import matrix33
BASE = np.float32([[1, 0, 0], [0, 1, 0], [0, 0, -1]])
SPEED = 2
MOUSE_SPEED = 1
MOUSE_SPEED = 1/8
@dataclass
class Picobot:
x: float
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
"""Game character.
@property
def pos(self):
"""Return position in a numpy array."""
return np.float32([self.x, self.y, self.z])
Parameters
----------
space : np.ndarray of shape (12, 12, 9) of bools
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
def pos(self, postion):
"""Set camera postion."""
self.x, self.y, self.z = postion
Attributes
----------
space : np.ndarray of shape (12, 12, 9) of bools
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):
"""Move in the given direction."""
"""Try to move in the given direction."""
dr = [right, upward, forward] @ self.rotation / self.fps * SPEED
x, y, z = self.pos + dr
if not self.space[int(x%12)][int(y%12)][int(z%9)]: self.pos += dr
self.pos = self.x % 12, self.y % 12, self.z % 9
x, y, z = [self.x, self.y, self.z] + dr
if self.empty(x, self.y, self.z): self.x = x % 12
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):
"""Look according to cursor position.
@ -58,6 +103,4 @@ class Picobot:
Present as a callback for GLFW CursorPos event.
"""
center = np.float32(glfw.get_window_size(window)) / 2
yaw, pitch = (center - [xpos, ypos]) / self.fps * MOUSE_SPEED
self.rotation = (matrix33.create_from_y_rotation(yaw) @
matrix33.create_from_x_rotation(pitch) @ self.rotation)
self.rotate(*((center - [xpos, ypos]) / self.fps * MOUSE_SPEED))

View File

@ -1,9 +1,15 @@
#version 330
uniform vec3 bg;
uniform vec3 color;
in float alpha;
in float depth;
out vec4 f_color;
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
uniform mat4 mvp;
uniform vec3 eye;
uniform vec3 camera;
uniform float visibility;
in vec3 in_vert;
out float alpha;
out float depth;
void main() {
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
# along with Axuy. If not, see <https://www.gnu.org/licenses/>.
from itertools import chain, combinations_with_replacement, permutations
from random import random
__doc__ = 'Axuy module for map class'
from itertools import product, starmap
from operator import add
import moderngl
import numpy as np
from pyrr import Matrix44
from .misc import hex2f4, resource_filename
from .misc import color, neighbors, resource_filename
from .pico import Picobot
# map.npy is generated by ../tools/mapgen
SPACE = np.load(resource_filename('map.npy'))
OXY = np.float32([[0, 0, 0], [1, 0, 0], [1, 1, 0],
[1, 1, 0], [0, 1, 0], [0, 0, 0]])
OYZ = np.float32([[0, 0, 0], [0, 1, 0], [0, 1, 1],
[0, 1, 1], [0, 0, 1], [0, 0, 0]])
OZX = np.float32([[0, 0, 0], [1, 0, 0], [1, 0, 1],
[1, 0, 1], [0, 0, 1], [0, 0, 0]])
NEIGHBORS = set(chain.from_iterable(map(
permutations, combinations_with_replacement((-1, 0, 1), 3))))
OCTAHEDRON = np.float32([[+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],
[-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.frag')) as f: FRAGMENT_SHADER = f.read()
@ -46,76 +59,77 @@ class View:
Parameters
----------
mapid : iterable of ints
mapid : iterable of length 48 of ints
order of nodes to sort map.npy.
context : moderngl.Context
OpenGL context from which ModernGL objects are created.
Attributes
----------
space : np.ndarray of bools
space : np.ndarray of shape (12, 12, 9) of bools
3D array of occupied space.
prog : moderngl.Program
Processed executable code in GLSL.
vao : moderngl.VertexArray
mapva : moderngl.VertexArray
Vertex data of the map.
camera : Picobot
Protagonist whose view is the camera.
"""
def __init__(self, mapid, context):
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])
for (a, b, c, d, e, f), occupied in np.ndenumerate(space):
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
self.space = np.zeros([12, 12, 9], dtype=bool)
for (i, j, k, x, y, z), occupied in np.ndenumerate(space):
if occupied: self.space[i*3 + x][j*3 + y][k*3 + z] = 1
vertices = []
for i in oxy: vertices.extend(i+j for j in OXY)
for i in oyz: vertices.extend(i+j for j in OYZ)
for i in ozx: vertices.extend(i+j for j in OZX)
for (x, y, z), occupied in np.ndenumerate(self.space):
if self.space[x][y][z-1] ^ occupied:
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,
fragment_shader=FRAGMENT_SHADER)
self.prog['color'].write(hex2f4('eeeeec').tobytes())
vbo = context.buffer(np.stack(vertices).astype('f4').tobytes())
self.vao = context.simple_vertex_array(self.prog, vbo, 'in_vert')
self.prog['bg'].write(color('Background').tobytes())
mapvb = context.buffer(np.stack(vertices).astype(np.float32).tobytes())
self.mapva = context.simple_vertex_array(self.prog, mapvb, 'in_vert')
x, y, z = random()*12, random()*12, random()*9
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)
self.camera = Picobot(self.space)
@property
def pos(self):
"""Return camera position in a numpy array."""
return self.camera.pos
"""Camera position in a NumPy array."""
return np.float32([self.camera.x, self.camera.y, self.camera.z])
@property
def right(self):
"""Return camera right direction."""
"""Camera right direction."""
return self.camera.rotation[0]
@property
def upward(self):
"""Return camera upward direction."""
"""Camera upward direction."""
return self.camera.rotation[1]
@property
def forward(self):
"""Return camera forward direction."""
"""Camera forward direction."""
return self.camera.rotation[2]
def render(self, width, height, fov):
"""Render the map."""
proj = Matrix44.perspective_projection(fov, width/height, 0.0001, 4)
look = Matrix44.look_at(self.pos, self.pos + self.forward, self.upward)
self.prog['mvp'].write((proj*look).astype(np.float32).tobytes())
self.prog['eye'].write(np.float32(self.pos).tobytes())
self.vao.render(moderngl.TRIANGLES)
visibility = 360 / fov
self.prog['visibility'].write(np.float32(visibility).tobytes())
self.prog['camera'].write(np.float32(self.pos).tobytes())
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)