Optimize a little bit
This commit is contained in:
parent
de0d42d0c9
commit
49b8f97d6d
|
@ -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)
|
||||||
|
|
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
|
# 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):
|
||||||
|
|
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
|
# 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)
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
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
|
# 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)
|
||||||
|
|
Loading…
Reference in New Issue