Optimize a little bit
This commit is contained in:
parent
de0d42d0c9
commit
49b8f97d6d
|
@ -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)
|
||||
|
|
27
axuy/misc.py
27
axuy/misc.py
|
@ -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):
|
||||
|
|
91
axuy/pico.py
91
axuy/pico.py
|
@ -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))
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
96
axuy/view.py
96
axuy/view.py
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue