387 lines
18 KiB
Python
387 lines
18 KiB
Python
|
from OpenGL.GL import *
|
||
|
import numpy as np
|
||
|
from OpenGL.GL.ARB.vertex_array_object import glDeleteVertexArrays
|
||
|
from OpenGL.GL.framebufferobjects import glBindRenderbuffer
|
||
|
from OpenGL.GLUT import *
|
||
|
import OpenGL.GLUT.freeglut
|
||
|
from OpenGL.GLU import *
|
||
|
from OpenGL.GL import *
|
||
|
from ctypes import sizeof, c_float, c_void_p, c_uint
|
||
|
|
||
|
from Lights.Spotlight.Spotlight import Spotlight
|
||
|
from WorldProvider.WorldProvider import WorldProvider
|
||
|
from MatrixStuff.Transformations import perspectiveMatrix, lookAt, translate, rotate
|
||
|
from Objects.Cube.Cube import Cube
|
||
|
from Objects.Cuboid.Cuboid import Cuboid
|
||
|
from Objects.World import World
|
||
|
import json
|
||
|
|
||
|
import random
|
||
|
import time
|
||
|
from scipy.signal import convolve
|
||
|
|
||
|
MAX_DISTANCE = 200.0
|
||
|
FRICTION_COEFFICENT = 0.9
|
||
|
EPSILON = 0.00001
|
||
|
|
||
|
def value_to_color(v, min_value, max_value):
|
||
|
r = g = b = 0.0
|
||
|
scope = max_value - min_value
|
||
|
normalized = (v - min_value) / (max_value - min_value)
|
||
|
if 0.5 * scope + min_value != 0:
|
||
|
b = max(0, 1.0 - abs(2.0 * normalized))
|
||
|
g = max(0, 1.0 - abs(2.0 * normalized - 1.0))
|
||
|
r = max(0, 1.0 - abs(2.0 * normalized - 2.0))
|
||
|
l = np.sqrt((r*r + b*b + g*g))
|
||
|
r /= l
|
||
|
g /= l
|
||
|
b /= l
|
||
|
return r, g, b
|
||
|
|
||
|
class Client:
|
||
|
def __init__(self, test=False, pos=[0, 0, 0]):
|
||
|
with open('./config.json', 'r') as f:
|
||
|
self.config = json.load(f)
|
||
|
glutInit(sys.argv)
|
||
|
self.width = 1920
|
||
|
self.height = 1080
|
||
|
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH)
|
||
|
glutInitWindowSize(self.width, self.height)
|
||
|
glutCreateWindow(b'Voxelengine')
|
||
|
|
||
|
with open('passthroughvertex.glsl', 'r') as f:
|
||
|
vertex_shader_string = f.read()
|
||
|
self.passthrough_vertex_shader_id = glCreateShader(GL_VERTEX_SHADER)
|
||
|
glShaderSource(self.passthrough_vertex_shader_id, vertex_shader_string)
|
||
|
glCompileShader(self.passthrough_vertex_shader_id)
|
||
|
if glGetShaderiv(self.passthrough_vertex_shader_id, GL_COMPILE_STATUS) != GL_TRUE:
|
||
|
raise RuntimeError(glGetShaderInfoLog(self.passthrough_vertex_shader_id))
|
||
|
|
||
|
with open('vertex.glsl', 'r') as f:
|
||
|
vertex_shader_string = f.read()
|
||
|
self.vertex_shader_id = glCreateShader(GL_VERTEX_SHADER)
|
||
|
glShaderSource(self.vertex_shader_id, vertex_shader_string)
|
||
|
glCompileShader(self.vertex_shader_id)
|
||
|
if glGetShaderiv(self.vertex_shader_id, GL_COMPILE_STATUS) != GL_TRUE:
|
||
|
raise RuntimeError(glGetShaderInfoLog(self.vertex_shader_id))
|
||
|
|
||
|
with open('fragment.glsl', 'r') as f:
|
||
|
fragment_shader_string = f.read()
|
||
|
self.fragment_shader_id = glCreateShader(GL_FRAGMENT_SHADER)
|
||
|
glShaderSource(self.fragment_shader_id, fragment_shader_string)
|
||
|
glCompileShader(self.fragment_shader_id)
|
||
|
if glGetShaderiv(self.fragment_shader_id, GL_COMPILE_STATUS) != GL_TRUE:
|
||
|
raise RuntimeError(glGetShaderInfoLog(self.fragment_shader_id))
|
||
|
|
||
|
Cube.initializeShader()
|
||
|
Cuboid.initializeShader()
|
||
|
self.geometry_shaders = {
|
||
|
Cube: Cube.GeometryShaderId,
|
||
|
Cuboid: Cuboid.GeometryShaderId
|
||
|
}
|
||
|
|
||
|
self.normal_program = {}
|
||
|
self.depth_program = {}
|
||
|
|
||
|
for key in self.geometry_shaders.keys():
|
||
|
self.normal_program[key] = glCreateProgram()
|
||
|
glAttachShader(self.normal_program[key], self.vertex_shader_id)
|
||
|
glAttachShader(self.normal_program[key], key.GeometryShaderId)
|
||
|
glAttachShader(self.normal_program[key], self.fragment_shader_id)
|
||
|
glLinkProgram(self.normal_program[key])
|
||
|
|
||
|
self.depth_program[self.normal_program[key]] = Spotlight.getDepthProgram(self.vertex_shader_id, key.GeometryShaderId)
|
||
|
|
||
|
self.world_provider = WorldProvider(self.normal_program)
|
||
|
for x_pos in range(0, 100):
|
||
|
for y_pos in range(0, 100):
|
||
|
for z_pos in range(0, 1):
|
||
|
self.world_provider.world.put_object(x_pos, y_pos, z_pos, Cuboid().setColor(
|
||
|
random.randint(0, 100) / 100.0, random.randint(0, 100) / 100.0, random.randint(0, 100) / 100.0))
|
||
|
|
||
|
self.projMatrix = perspectiveMatrix(45.0, 400 / 400, 0.01, MAX_DISTANCE)
|
||
|
|
||
|
self.rx = self.cx = self.cy = 0
|
||
|
self.opening = 45
|
||
|
|
||
|
glutReshapeFunc(self.resize)
|
||
|
glutDisplayFunc(self.display)
|
||
|
glutKeyboardFunc(self.keyboardHandler)
|
||
|
glutSpecialFunc(self.funcKeydHandler)
|
||
|
|
||
|
self.pos = pos
|
||
|
|
||
|
self.time = time.time()
|
||
|
|
||
|
self.heat_map = np.zeros((100, 100, 1))
|
||
|
|
||
|
self.v_map_x = np.zeros((100, 100, 1))
|
||
|
self.v_map_y = np.zeros((100, 100, 1))
|
||
|
self.v_map_z = np.zeros((100, 100, 1))
|
||
|
|
||
|
if not test:
|
||
|
glutMainLoop()
|
||
|
else:
|
||
|
self.display()
|
||
|
self.resize(100, 100)
|
||
|
|
||
|
|
||
|
|
||
|
def display(self):
|
||
|
glClearColor(0, 0, 0, 0)
|
||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||
|
projMatrix = perspectiveMatrix(45, float(self.width) / float(self.height), 0.01, MAX_DISTANCE)
|
||
|
|
||
|
world: World = self.world_provider.world
|
||
|
lights = world.get_lights_to_render(self.pos, self.config['render_light_distance'])
|
||
|
for light in lights:
|
||
|
light.prepareForDepthMapping()
|
||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||
|
light_mat = translate(light.pos[0], light.pos[1], light.pos[2]) * \
|
||
|
lookAt(0, 0, 0, -light.pos[0], -light.pos[1], -light.pos[2], 0, 1, 0) * \
|
||
|
perspectiveMatrix(90, float(light.map_size) / float(light.map_size), 0.01, MAX_DISTANCE)
|
||
|
|
||
|
for obj_type, program_id in self.depth_program.items():
|
||
|
glUseProgram(program_id)
|
||
|
widthid = glGetUniformLocation(program_id, 'width')
|
||
|
heightid = glGetUniformLocation(program_id, 'height')
|
||
|
nearid = glGetUniformLocation(program_id, 'near')
|
||
|
farid = glGetUniformLocation(program_id, 'far')
|
||
|
glUniform1f(nearid, 0.01)
|
||
|
glUniform1f(farid, 100)
|
||
|
glUniform1f(widthid, light.map_size)
|
||
|
glUniform1f(heightid, light.map_size)
|
||
|
|
||
|
world.render(light_mat, rotate(0, 0, 0), self.depth_program)
|
||
|
glFlush()
|
||
|
light.finishDepthMapping()
|
||
|
glClearColor(0, 0, 0, 0)
|
||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||
|
|
||
|
glClearColor(0, 0, 0, 0)
|
||
|
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
|
||
|
|
||
|
for obj_type, program_id in self.normal_program.items():
|
||
|
glUseProgram(program_id)
|
||
|
widthid = glGetUniformLocation(program_id, 'width')
|
||
|
heightid = glGetUniformLocation(program_id, 'height')
|
||
|
nearid = glGetUniformLocation(program_id, 'near')
|
||
|
farid = glGetUniformLocation(program_id, 'far')
|
||
|
glUniform1f(nearid, 0.01)
|
||
|
glUniform1f(farid, 100)
|
||
|
glUniform1f(widthid, self.width)
|
||
|
glUniform1f(heightid, self.height)
|
||
|
|
||
|
world.render(translate(self.pos[0], self.pos[1], self.pos[2]) * lookAt(0, 0, 0, 0, 0, -self.pos[2], 0, 1, 0) * projMatrix, rotate(0, 0, 0))
|
||
|
glFlush()
|
||
|
|
||
|
glutSwapBuffers()
|
||
|
|
||
|
max_value = np.max(self.heat_map)
|
||
|
# min_value = np.min(self.heat_map)
|
||
|
min_value = 0
|
||
|
|
||
|
vel = np.sqrt(np.square(self.v_map_x) + np.square(self.v_map_y) + np.square(self.v_map_z))
|
||
|
max_value = np.max(vel)
|
||
|
min_value = np.min(vel)
|
||
|
|
||
|
for x_pos in range(0, 100):
|
||
|
for y_pos in range(0, 100):
|
||
|
for z_pos in range(0, 1):
|
||
|
# r, g, b = value_to_color(self.heat_map[x_pos, y_pos, z_pos], min_value, max_value)
|
||
|
r, g, b = value_to_color(vel[x_pos, y_pos, z_pos], min_value, max_value)
|
||
|
|
||
|
self.world_provider.world.set_color(x_pos, y_pos, z_pos, r, g, b)
|
||
|
# friction
|
||
|
# self.heat_map += np.sqrt(np.square(self.v_map_x * (1.0 - FRICTION_COEFFICENT)) +
|
||
|
# np.square(self.v_map_y * (1.0 - FRICTION_COEFFICENT)) +
|
||
|
# np.square(self.v_map_z * (1.0 - FRICTION_COEFFICENT)))
|
||
|
self.v_map_x *= FRICTION_COEFFICENT
|
||
|
self.v_map_y *= FRICTION_COEFFICENT
|
||
|
self.v_map_z *= FRICTION_COEFFICENT
|
||
|
# hot stuff rises / cool stuff sinks
|
||
|
rise = self.heat_map[:, :-1, :] > (self.heat_map[:, 1:, :] + EPSILON)
|
||
|
self.v_map_y[:, :-1, :] += 1.0 * rise
|
||
|
sink = self.heat_map[:, :-1, :] < (self.heat_map[:, 1:, :] - EPSILON)
|
||
|
self.v_map_y[:, 1:, :] -= 1.0 * sink
|
||
|
#flow
|
||
|
new_v_map_x = np.zeros(self.v_map_x.shape)
|
||
|
new_v_map_y = np.zeros(self.v_map_x.shape)
|
||
|
new_v_map_z = np.zeros(self.v_map_x.shape)
|
||
|
for x_pos in range(self.v_map_x.shape[0]):
|
||
|
for y_pos in range(self.v_map_x.shape[1]):
|
||
|
for z_pos in range(self.v_map_x.shape[2]):
|
||
|
target_x = min(self.v_map_x.shape[0] - 1,
|
||
|
max(0, int(round(x_pos + self.v_map_x[x_pos, y_pos, z_pos]))))
|
||
|
target_y = min(self.v_map_x.shape[1] - 1,
|
||
|
max(0, int(round(y_pos + self.v_map_y[x_pos, y_pos, z_pos]))))
|
||
|
target_z = min(self.v_map_x.shape[2] - 1,
|
||
|
max(0, int(round(z_pos + self.v_map_z[x_pos, y_pos, z_pos]))))
|
||
|
|
||
|
friction_dispersion = (1.0 -FRICTION_COEFFICENT) / 4
|
||
|
# velocity dispersion
|
||
|
# x
|
||
|
new_v_map_x[target_x, target_y, target_z] += self.v_map_x[x_pos, y_pos, z_pos] * FRICTION_COEFFICENT
|
||
|
if target_y + 1 < self.v_map_x.shape[1] - 1:
|
||
|
new_v_map_y[target_x, target_y + 1, target_z] += self.v_map_x[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_x[target_x, target_y, target_z] += self.v_map_x[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
if target_y - 1 > 0:
|
||
|
new_v_map_y[target_x, target_y - 1, target_z] -= self.v_map_x[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_x[target_x, target_y, target_z] += self.v_map_x[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
|
||
|
if target_z + 1 < self.v_map_x.shape[2] - 1:
|
||
|
new_v_map_z[target_x, target_y, target_z + 1] += self.v_map_x[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_x[target_x, target_y, target_z] += self.v_map_x[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
if target_z - 1 > 0:
|
||
|
new_v_map_z[target_x, target_y, target_z - 1] -= self.v_map_x[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_x[target_x, target_y, target_z] += self.v_map_x[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
# y
|
||
|
new_v_map_y[target_x, target_y, target_z] += self.v_map_y[x_pos, y_pos, z_pos] * FRICTION_COEFFICENT
|
||
|
if target_x + 1 < self.v_map_x.shape[0] - 1:
|
||
|
new_v_map_x[target_x + 1, target_y, target_z] += self.v_map_y[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_y[target_x, target_y, target_z] += self.v_map_y[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
if target_x - 1 > 0:
|
||
|
new_v_map_x[target_x - 1, target_y, target_z] -= self.v_map_y[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_y[target_x, target_y, target_z] += self.v_map_y[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
|
||
|
if target_z + 1 < self.v_map_x.shape[2] - 1:
|
||
|
new_v_map_z[target_x, target_y, target_z + 1] += self.v_map_y[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_y[target_x, target_y, target_z] += self.v_map_y[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
if target_z - 1 > 0:
|
||
|
new_v_map_z[target_x, target_y, target_z - 1] -= self.v_map_y[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_y[target_x, target_y, target_z] += self.v_map_y[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
# z
|
||
|
new_v_map_z[target_x, target_y, target_z] += self.v_map_z[x_pos, y_pos, z_pos] * FRICTION_COEFFICENT
|
||
|
if target_x + 1 < self.v_map_x.shape[0] - 1:
|
||
|
new_v_map_x[target_x + 1, target_y, target_z] += self.v_map_z[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_z[target_x, target_y, target_z] += self.v_map_z[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
if target_x - 1 > 0:
|
||
|
new_v_map_x[target_x - 1, target_y, target_z] -= self.v_map_z[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_z[target_x, target_y, target_z] += self.v_map_z[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
|
||
|
if target_y + 1 < self.v_map_x.shape[1] - 1:
|
||
|
new_v_map_y[target_x, target_y + 1, target_z] += self.v_map_z[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_z[target_x, target_y, target_z] += self.v_map_z[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
if target_y - 1 > 0:
|
||
|
new_v_map_y[target_x, target_y - 1, target_z] -= self.v_map_z[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
else:
|
||
|
new_v_map_z[target_x, target_y, target_z] += self.v_map_z[x_pos, y_pos, z_pos] * friction_dispersion
|
||
|
# handle boundaries
|
||
|
filter_mat = np.array([[-1.0], [0], [1.0]]) / 2.0
|
||
|
new_v_map_y[0, :, :] += convolve(new_v_map_x[0, :, :], filter_mat, 'same')
|
||
|
new_v_map_x[0, :, :] = 0
|
||
|
new_v_map_y[new_v_map_x.shape[0] - 1, :, :] +=\
|
||
|
convolve(new_v_map_x[new_v_map_x.shape[0] - 1, :, :], filter_mat, 'same')
|
||
|
new_v_map_x[new_v_map_x.shape[0] - 1, :, :] = 0
|
||
|
|
||
|
filter_mat = np.array([[-1.0], [0], [1.0]]) / 2.0
|
||
|
new_v_map_x[:, 0, :] += convolve(new_v_map_y[:, 0, :], filter_mat, 'same')
|
||
|
new_v_map_y[:, 0, :] = 0
|
||
|
new_v_map_x[:, new_v_map_x.shape[1] - 1, :] +=\
|
||
|
convolve(new_v_map_y[:, new_v_map_x.shape[1] - 1, :], filter_mat, 'same')
|
||
|
new_v_map_y[:, new_v_map_x.shape[1] - 1, :] = 0
|
||
|
|
||
|
# corners
|
||
|
new_v_map_x[0, 0, 0] = new_v_map_y[0, 0, 0] = new_v_map_z[0, 0, 0] = 0
|
||
|
new_v_map_x[-1, 0, 0] = new_v_map_y[-1, 0, 0] = new_v_map_z[-1, 0, 0] = 0
|
||
|
new_v_map_x[-1, -1, 0] = new_v_map_y[-1, -1, 0] = new_v_map_z[-1, -1, 0] = 0
|
||
|
new_v_map_x[-1, -1, -1] = new_v_map_y[-1, -1, -1] = new_v_map_z[-1, -1, -1] = 0
|
||
|
new_v_map_x[0, -1, -1] = new_v_map_y[0, -1, -1] = new_v_map_z[0, -1, -1] = 0
|
||
|
new_v_map_x[0, -1, 0] = new_v_map_y[0, -1, 0] = new_v_map_z[0, -1, 0] = 0
|
||
|
new_v_map_x[-1, -1, 0] = new_v_map_y[-1, -1, 0] = new_v_map_z[-1, -1, 0] = 0
|
||
|
new_v_map_x[-1, 0, -1] = new_v_map_y[-1, 0, -1] = new_v_map_z[-1, 0, -1] = 0
|
||
|
|
||
|
self.v_map_x = new_v_map_x
|
||
|
self.v_map_y = new_v_map_y
|
||
|
self.v_map_z = new_v_map_z
|
||
|
|
||
|
filter_mat = (np.zeros((3, 3, 1)) + 1.0) / 9.0
|
||
|
|
||
|
v_map = np.pad(self.v_map_x, 1, 'edge')
|
||
|
self.v_map_x = convolve(v_map, filter_mat, mode='same')[1:-1, 1:-1, 1:2]
|
||
|
v_map = np.pad(self.v_map_y, 1, 'edge')
|
||
|
self.v_map_y = convolve(v_map, filter_mat, mode='same')[1:-1, 1:-1, 1:2]
|
||
|
v_map = np.pad(self.v_map_z, 1, 'edge')
|
||
|
self.v_map_z = convolve(v_map, filter_mat, mode='same')[1:-1, 1:-1, 1:2]
|
||
|
# moving heat
|
||
|
heat_map = np.zeros(self.heat_map.shape)
|
||
|
for x_pos in range(self.v_map_x.shape[0]):
|
||
|
for y_pos in range(self.v_map_x.shape[1]):
|
||
|
for z_pos in range(self.v_map_x.shape[2]):
|
||
|
target_x = min(self.v_map_x.shape[0] - 1,
|
||
|
max(0, int(round(x_pos + self.v_map_x[x_pos, y_pos, z_pos]))))
|
||
|
target_y = min(self.v_map_x.shape[1] - 1,
|
||
|
max(0, int(round(y_pos + self.v_map_y[x_pos, y_pos, z_pos]))))
|
||
|
target_z = min(self.v_map_x.shape[2] - 1,
|
||
|
max(0, int(round(z_pos + self.v_map_z[x_pos, y_pos, z_pos]))))
|
||
|
heat_map[target_x, target_y, target_z] += self.heat_map[x_pos, y_pos, z_pos]
|
||
|
|
||
|
self.heat_map = heat_map
|
||
|
# dispersing heat
|
||
|
heat_map = np.pad(self.heat_map, 1, 'edge')
|
||
|
self.heat_map = convolve(heat_map, filter_mat, mode='same')[1:-1, 1:-1, 1:2]
|
||
|
# heat source keeps source block on constant heat
|
||
|
self.heat_map[50-5:50+5, 0, 0] = 100.0
|
||
|
# roof gets cooled off to min temp
|
||
|
self.heat_map[:, 99, :] = np.maximum(self.heat_map[:, 99, :] * 0.8, 0.0)
|
||
|
|
||
|
print(1.0 / (time.time() - self.time))
|
||
|
self.time = time.time()
|
||
|
glutPostRedisplay()
|
||
|
|
||
|
def resize(self, w, h):
|
||
|
w = max(w, 1)
|
||
|
h = max(h, 1)
|
||
|
glViewport(0, 0, w, h)
|
||
|
self.projMatrix = perspectiveMatrix(45.0, float(w) / float(h), 0.01, MAX_DISTANCE)
|
||
|
self.width = w
|
||
|
self.height = h
|
||
|
|
||
|
def keyboardHandler(self, key: int, x: int, y: int):
|
||
|
if key == b'\x1b':
|
||
|
exit()
|
||
|
|
||
|
if key == b'+':
|
||
|
self.rx += 0.25
|
||
|
if key == b'-':
|
||
|
self.rx -= 0.25
|
||
|
|
||
|
if key == b'w':
|
||
|
self.cy += 0.25
|
||
|
if key == b's':
|
||
|
self.cy -= 0.25
|
||
|
|
||
|
if key == b'a':
|
||
|
self.cx -= 0.25
|
||
|
if key == b'd':
|
||
|
self.cx += 0.25
|
||
|
|
||
|
if key == b'q':
|
||
|
self.opening -= 0.25
|
||
|
if key == b'e':
|
||
|
self.opening += 0.25
|
||
|
|
||
|
if key == b'r':
|
||
|
print(self.cx, self.cy, self.opening)
|
||
|
# glutPostRedisplay()
|
||
|
# print(key,x,y)
|
||
|
|
||
|
def funcKeydHandler(self, key: int, x: int, y: int):
|
||
|
if key == 11:
|
||
|
glutFullScreenToggle()
|
||
|
# print(key)
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
client = Client(pos=[-50, -50, -200])
|