Working, but...
This commit is contained in:
parent
0c042c79d7
commit
b1edaf6fc1
20 changed files with 807 additions and 0 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -160,3 +160,6 @@ cython_debug/
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
/__pycache__/
|
||||||
|
/mesh/__pycache__/
|
||||||
|
/world_objects/__pycache__/
|
||||||
|
|
BIN
assets/frame.png
Normal file
BIN
assets/frame.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 229 B |
BIN
assets/test.png
Normal file
BIN
assets/test.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
59
camera.py
Normal file
59
camera.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
import glm
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
|
||||||
|
|
||||||
|
class Camera:
|
||||||
|
def __init__(self, position, yaw, pitch):
|
||||||
|
self.position = glm.vec3(position)
|
||||||
|
self.yaw = glm.radians(yaw)
|
||||||
|
self.pitch = glm.radians(pitch)
|
||||||
|
|
||||||
|
self.up = glm.vec3(0, 1, 0)
|
||||||
|
self.right = glm.vec3(1, 0, 0)
|
||||||
|
self.forward = glm.vec3(0, 0, -1)
|
||||||
|
|
||||||
|
self.m_proj = glm.perspective(V_FOV, ASPECT_RATIO, NEAR, FAR)
|
||||||
|
self.m_view = glm.mat4()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.update_vectors()
|
||||||
|
self.update_view_matrix()
|
||||||
|
|
||||||
|
def update_view_matrix(self):
|
||||||
|
self.m_view = glm.lookAt(self.position, self.position + self.forward, self.up)
|
||||||
|
|
||||||
|
def update_vectors(self):
|
||||||
|
self.forward.x = glm.cos(self.yaw) * glm.cos(self.pitch)
|
||||||
|
self.forward.y = glm.sin(self.pitch)
|
||||||
|
self.forward.z = glm.sin(self.yaw) * glm.cos(self.pitch)
|
||||||
|
|
||||||
|
self.forward = glm.normalize(self.forward)
|
||||||
|
self.right = glm.normalize(glm.cross(self.forward, glm.vec3(0, 1, 0)))
|
||||||
|
self.up = glm.normalize(glm.cross(self.right, self.forward))
|
||||||
|
|
||||||
|
def rotate_pitch(self, delta_y):
|
||||||
|
self.pitch -= delta_y
|
||||||
|
self.pitch = glm.clamp(self.pitch, -PITCH_MAX, PITCH_MAX)
|
||||||
|
|
||||||
|
def rotate_yaw(self, delta_x):
|
||||||
|
self.yaw += delta_x
|
||||||
|
|
||||||
|
def move_left(self, velocity):
|
||||||
|
self.position -= self.right * velocity
|
||||||
|
|
||||||
|
def move_right(self, velocity):
|
||||||
|
self.position += self.right * velocity
|
||||||
|
|
||||||
|
def move_up(self, velocity):
|
||||||
|
self.position += self.up * velocity
|
||||||
|
|
||||||
|
def move_down(self, velocity):
|
||||||
|
self.position -= self.up * velocity
|
||||||
|
|
||||||
|
def move_forward(self, velocity):
|
||||||
|
self.position += self.forward * velocity
|
||||||
|
|
||||||
|
def move_back(self, velocity):
|
||||||
|
self.position -= self.forward * velocity
|
75
engine.py
Normal file
75
engine.py
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
import moderngl as mgl
|
||||||
|
import pygame as pg
|
||||||
|
import sys
|
||||||
|
from shader_program import ShaderProgram
|
||||||
|
from scene import Scene
|
||||||
|
from player import Player
|
||||||
|
from textures import Textures
|
||||||
|
|
||||||
|
|
||||||
|
class VoxelEngine:
|
||||||
|
def __init__(self):
|
||||||
|
pg.init()
|
||||||
|
pg.display.gl_set_attribute(pg.GL_CONTEXT_MAJOR_VERSION, 3)
|
||||||
|
pg.display.gl_set_attribute(pg.GL_CONTEXT_MINOR_VERSION, 3)
|
||||||
|
pg.display.gl_set_attribute(pg.GL_CONTEXT_PROFILE_MASK, pg.GL_CONTEXT_PROFILE_CORE)
|
||||||
|
pg.display.gl_set_attribute(pg.GL_DEPTH_SIZE, 24)
|
||||||
|
|
||||||
|
pg.display.set_mode(WIN_RES, flags=pg.OPENGL | pg.DOUBLEBUF)
|
||||||
|
self.ctx = mgl.create_context()
|
||||||
|
|
||||||
|
self.ctx.enable(flags=mgl.DEPTH_TEST | mgl.CULL_FACE | mgl.BLEND)
|
||||||
|
self.ctx.gc_mode = "auto"
|
||||||
|
|
||||||
|
self.clock = pg.time.Clock()
|
||||||
|
self.delta_time = 0
|
||||||
|
self.time = 0
|
||||||
|
|
||||||
|
pg.event.set_grab(True)
|
||||||
|
pg.mouse.set_visible(False)
|
||||||
|
|
||||||
|
self.on_init()
|
||||||
|
|
||||||
|
self.running = True
|
||||||
|
|
||||||
|
def on_init(self):
|
||||||
|
self.textures = Textures(self)
|
||||||
|
self.player = Player(self)
|
||||||
|
self.shader_program = ShaderProgram(self)
|
||||||
|
self.scene = Scene(self)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.player.update()
|
||||||
|
self.shader_program.update()
|
||||||
|
self.scene.update()
|
||||||
|
|
||||||
|
self.delta_time = self.clock.tick()
|
||||||
|
self.time = pg.time.get_ticks() * 0.001
|
||||||
|
pg.display.set_caption(f"{self.clock.get_fps() :.0f}")
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
self.ctx.clear(color=BG_COLOR)
|
||||||
|
self.scene.render()
|
||||||
|
pg.display.flip()
|
||||||
|
|
||||||
|
def handle_events(self):
|
||||||
|
for event in pg.event.get():
|
||||||
|
if event.type == pg.QUIT or (event.type == pg.KEYDOWN and event.key == pg.K_ESCAPE):
|
||||||
|
self.running = False
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
while self.running:
|
||||||
|
self.handle_events()
|
||||||
|
self.update()
|
||||||
|
self.render()
|
||||||
|
|
||||||
|
pg.quit()
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = VoxelEngine()
|
||||||
|
app.run()
|
32
mesh/base.py
Normal file
32
mesh/base.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMesh:
|
||||||
|
def __init__(self):
|
||||||
|
# OpenGL context
|
||||||
|
self.ctx = None
|
||||||
|
# shader program
|
||||||
|
self.program = None
|
||||||
|
# vertex buffer data type format: "3f 3f"
|
||||||
|
self.vbo_format = None
|
||||||
|
# attribute names according to the format: ("in_position", "in_color")
|
||||||
|
self.attributes: tuple[str, ...] = None
|
||||||
|
# vertex array object
|
||||||
|
self.vao = None
|
||||||
|
|
||||||
|
def get_vertex_data(self) -> numpy.array: ...
|
||||||
|
|
||||||
|
def get_vao(self):
|
||||||
|
vertex_data = self.get_vertex_data()
|
||||||
|
vbo = self.ctx.buffer(vertex_data)
|
||||||
|
vao = self.ctx.vertex_array(
|
||||||
|
self.program,
|
||||||
|
[(vbo, self.vbo_format, *self.attributes)],
|
||||||
|
skip_errors=True
|
||||||
|
)
|
||||||
|
return vao
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
self.vao.render()
|
28
mesh/chunk.py
Normal file
28
mesh/chunk.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from mesh.base import BaseMesh
|
||||||
|
from mesh.chunk_builder import build_chunk_mesh
|
||||||
|
|
||||||
|
|
||||||
|
class ChunkMesh(BaseMesh):
|
||||||
|
def __init__(self, chunk):
|
||||||
|
super().__init__()
|
||||||
|
self.app = chunk.app
|
||||||
|
self.chunk = chunk
|
||||||
|
self.ctx = self.app.ctx
|
||||||
|
self.program = self.app.shader_program.chunk
|
||||||
|
|
||||||
|
self.vbo_format = "3u1 1u1 1u1 1u1 1u1"
|
||||||
|
self.format_size = sum(int(fmt[:1]) for fmt in self.vbo_format.split())
|
||||||
|
self.attributes = ("in_position", "voxel_id", "face_id", "ao_id", "flip_id")
|
||||||
|
self.vao = self.get_vao()
|
||||||
|
|
||||||
|
def get_vertex_data(self):
|
||||||
|
mesh = build_chunk_mesh(
|
||||||
|
chunk_voxels=self.chunk.voxels,
|
||||||
|
format_size=self.format_size,
|
||||||
|
chunk_pos=self.chunk.position,
|
||||||
|
world_voxels=self.chunk.world.voxels
|
||||||
|
)
|
||||||
|
|
||||||
|
return mesh
|
211
mesh/chunk_builder.py
Normal file
211
mesh/chunk_builder.py
Normal file
|
@ -0,0 +1,211 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
from numba import uint8
|
||||||
|
|
||||||
|
|
||||||
|
@njit
|
||||||
|
def get_ao(local_pos, world_pos, world_voxels, plane):
|
||||||
|
x, y, z = local_pos
|
||||||
|
wx, wy, wz = world_pos
|
||||||
|
|
||||||
|
if plane == "Y":
|
||||||
|
a = is_void((x , y, z - 1), (wx , wy, wz - 1), world_voxels)
|
||||||
|
b = is_void((x - 1, y, z - 1), (wx - 1, wy, wz - 1), world_voxels)
|
||||||
|
c = is_void((x - 1, y, z ), (wx - 1, wy, wz ), world_voxels)
|
||||||
|
d = is_void((x - 1, y, z + 1), (wx - 1, wy, wz + 1), world_voxels)
|
||||||
|
e = is_void((x , y, z + 1), (wx , wy, wz + 1), world_voxels)
|
||||||
|
f = is_void((x + 1, y, z + 1), (wx + 1, wy, wz + 1), world_voxels)
|
||||||
|
g = is_void((x + 1, y, z ), (wx + 1, wy, wz ), world_voxels)
|
||||||
|
h = is_void((x + 1, y, z - 1), (wx + 1, wy, wz - 1), world_voxels)
|
||||||
|
|
||||||
|
elif plane == "X":
|
||||||
|
a = is_void((x, y , z - 1), (wx, wy , wz - 1), world_voxels)
|
||||||
|
b = is_void((x, y - 1, z - 1), (wx, wy - 1, wz - 1), world_voxels)
|
||||||
|
c = is_void((x, y - 1, z ), (wx, wy - 1, wz ), world_voxels)
|
||||||
|
d = is_void((x, y - 1, z + 1), (wx, wy - 1, wz + 1), world_voxels)
|
||||||
|
e = is_void((x, y , z + 1), (wx, wy , wz + 1), world_voxels)
|
||||||
|
f = is_void((x, y + 1, z + 1), (wx, wy + 1, wz + 1), world_voxels)
|
||||||
|
g = is_void((x, y + 1, z ), (wx, wy + 1, wz ), world_voxels)
|
||||||
|
h = is_void((x, y + 1, z - 1), (wx, wy + 1, wz - 1), world_voxels)
|
||||||
|
|
||||||
|
else: # Z plane
|
||||||
|
a = is_void((x - 1, y , z), (wx - 1, wy , z), world_voxels)
|
||||||
|
b = is_void((x - 1, y - 1, z), (wx - 1, wy - 1, z), world_voxels)
|
||||||
|
c = is_void((x , y - 1, z), (wx , wy - 1, z), world_voxels)
|
||||||
|
d = is_void((x + 1, y - 1, z), (wx + 1, wy - 1, z), world_voxels)
|
||||||
|
e = is_void((x + 1, y , z), (wx + 1, wy , z), world_voxels)
|
||||||
|
f = is_void((x + 1, y + 1, z), (wx + 1, wy + 1, z), world_voxels)
|
||||||
|
g = is_void((x , y + 1, z), (wx , wy + 1, z), world_voxels)
|
||||||
|
h = is_void((x - 1, y + 1, z), (wx - 1, wy + 1, z), world_voxels)
|
||||||
|
|
||||||
|
ao = (a + b + c), (g + h + a), (e + f + g), (c + d + e)
|
||||||
|
return ao
|
||||||
|
|
||||||
|
|
||||||
|
@njit
|
||||||
|
def to_uint8(x, y, z, voxel_id, face_id, ao_id, flip_id):
|
||||||
|
return (uint8(x), uint8(y), uint8(z),
|
||||||
|
uint8(voxel_id), uint8(face_id), uint8(ao_id), uint8(flip_id)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@njit
|
||||||
|
def get_chunk_index(world_voxel_pos):
|
||||||
|
wx, wy, wz = world_voxel_pos
|
||||||
|
cx = wx // CHUNK_SIZE
|
||||||
|
cy = wy // CHUNK_SIZE
|
||||||
|
cz = wz // CHUNK_SIZE
|
||||||
|
|
||||||
|
if not (0 <= cx < WORLD_W and 0 <= cy < WORLD_H and 0 <= cz < WORLD_D):
|
||||||
|
return -1
|
||||||
|
|
||||||
|
index = cx + WORLD_W * cz + WORLD_AREA * cy
|
||||||
|
return index
|
||||||
|
|
||||||
|
|
||||||
|
@njit
|
||||||
|
def is_void(local_voxel_pos, world_voxel_pos, world_voxels):
|
||||||
|
chunk_index = get_chunk_index(world_voxel_pos)
|
||||||
|
|
||||||
|
if chunk_index == -1:
|
||||||
|
return False
|
||||||
|
|
||||||
|
chunk_voxels = world_voxels[chunk_index]
|
||||||
|
x, y, z = local_voxel_pos
|
||||||
|
voxel_index = x % CHUNK_SIZE + z % CHUNK_SIZE * CHUNK_SIZE + y % CHUNK_SIZE * CHUNK_AREA
|
||||||
|
|
||||||
|
if chunk_voxels[voxel_index]:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
@njit
|
||||||
|
def add_data(vertex_data, index, *vertices):
|
||||||
|
for vertex in vertices:
|
||||||
|
for attribute in vertex:
|
||||||
|
vertex_data[index] = attribute
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
return index
|
||||||
|
|
||||||
|
|
||||||
|
@njit
|
||||||
|
def build_chunk_mesh(chunk_voxels, format_size, chunk_pos, world_voxels):
|
||||||
|
vertex_data = numpy.empty(CHUNK_VOL * 18 * format_size, dtype="uint8")
|
||||||
|
index = 0
|
||||||
|
|
||||||
|
for x in range(CHUNK_SIZE):
|
||||||
|
for y in range(CHUNK_SIZE):
|
||||||
|
for z in range(CHUNK_SIZE):
|
||||||
|
voxel_id = chunk_voxels[x + CHUNK_SIZE * z + CHUNK_AREA * y]
|
||||||
|
|
||||||
|
if not voxel_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# voxel world position
|
||||||
|
cx, cy, cz = chunk_pos
|
||||||
|
wx = x + cx * CHUNK_SIZE
|
||||||
|
wy = y + cy * CHUNK_SIZE
|
||||||
|
wz = z + cz * CHUNK_SIZE
|
||||||
|
|
||||||
|
# top face
|
||||||
|
if is_void((x, y + 1, z), (wx, wy + 1, wz), world_voxels):
|
||||||
|
# get ao values
|
||||||
|
ao = get_ao((x, y + 1, z), (wx, wy + 1, wz), world_voxels, plane="Y")
|
||||||
|
flip_id = ao[1] + ao[3] > ao[0] + ao[2]
|
||||||
|
|
||||||
|
# format: x, y, z, voxel_id, face_id, ao_id
|
||||||
|
v0 = to_uint8(x , y + 1, z , voxel_id, 0, ao[0], flip_id)
|
||||||
|
v1 = to_uint8(x + 1, y + 1, z , voxel_id, 0, ao[1], flip_id)
|
||||||
|
v2 = to_uint8(x + 1, y + 1, z + 1, voxel_id, 0, ao[2], flip_id)
|
||||||
|
v3 = to_uint8(x , y + 1, z + 1, voxel_id, 0, ao[3], flip_id)
|
||||||
|
|
||||||
|
if flip_id:
|
||||||
|
index = add_data(vertex_data, index, v1, v0, v3, v1, v3, v2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
index = add_data(vertex_data, index, v0, v3, v2, v0, v2, v1)
|
||||||
|
|
||||||
|
# bottom face
|
||||||
|
if is_void((x, y - 1, z), (wx, wy - 1, wz), world_voxels):
|
||||||
|
ao = get_ao((x, y - 1, z), (wx, wy - 1, wz), world_voxels, plane="Y")
|
||||||
|
flip_id = ao[1] + ao[3] > ao[0] + ao[2]
|
||||||
|
|
||||||
|
v0 = to_uint8(x , y, z , voxel_id, 1, ao[0], flip_id)
|
||||||
|
v1 = to_uint8(x + 1, y, z , voxel_id, 1, ao[1], flip_id)
|
||||||
|
v2 = to_uint8(x + 1, y, z + 1, voxel_id, 1, ao[2], flip_id)
|
||||||
|
v3 = to_uint8(x , y, z + 1, voxel_id, 1, ao[3], flip_id)
|
||||||
|
|
||||||
|
if flip_id:
|
||||||
|
index = add_data(vertex_data, index, v1, v3, v0, v1, v2, v3)
|
||||||
|
|
||||||
|
else:
|
||||||
|
index = add_data(vertex_data, index, v0, v2, v3, v0, v1, v2)
|
||||||
|
|
||||||
|
# right face
|
||||||
|
if is_void((x + 1, y, z), (wx + 1, wy, wz), world_voxels):
|
||||||
|
ao = get_ao((x + 1, y, z), (wx + 1, wy, wz), world_voxels, plane="X")
|
||||||
|
flip_id = ao[1] + ao[3] > ao[0] + ao[2]
|
||||||
|
|
||||||
|
v0 = to_uint8(x + 1, y , z , voxel_id, 2, ao[0], flip_id)
|
||||||
|
v1 = to_uint8(x + 1, y + 1, z , voxel_id, 2, ao[1], flip_id)
|
||||||
|
v2 = to_uint8(x + 1, y + 1, z + 1, voxel_id, 2, ao[2], flip_id)
|
||||||
|
v3 = to_uint8(x + 1, y , z + 1, voxel_id, 2, ao[3], flip_id)
|
||||||
|
|
||||||
|
if flip_id:
|
||||||
|
index = add_data(vertex_data, index, v3, v0, v1, v3, v1, v2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
index = add_data(vertex_data, index, v0, v1, v2, v0, v2, v3)
|
||||||
|
|
||||||
|
# left face
|
||||||
|
if is_void((x - 1, y, z), (wx - 1, wy, wz), world_voxels):
|
||||||
|
ao = get_ao((x - 1, y, z), (wx - 1, wy, wz), world_voxels, plane="X")
|
||||||
|
flip_id = ao[1] + ao[3] > ao[0] + ao[2]
|
||||||
|
|
||||||
|
v0 = to_uint8(x, y , z , voxel_id, 3, ao[0], flip_id)
|
||||||
|
v1 = to_uint8(x, y + 1, z , voxel_id, 3, ao[1], flip_id)
|
||||||
|
v2 = to_uint8(x, y + 1, z + 1, voxel_id, 3, ao[2], flip_id)
|
||||||
|
v3 = to_uint8(x, y , z + 1, voxel_id, 3, ao[3], flip_id)
|
||||||
|
|
||||||
|
if flip_id:
|
||||||
|
index = add_data(vertex_data, index, v3, v1, v0, v3, v2, v1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
index = add_data(vertex_data, index, v0, v2, v1, v0, v3, v2)
|
||||||
|
|
||||||
|
# back face
|
||||||
|
if is_void((x, y, z - 1), (wx, wy, wz - 1), world_voxels):
|
||||||
|
ao = get_ao((x, y, z - 1), (wx, wy, wz - 1), world_voxels, plane="Z")
|
||||||
|
flip_id = ao[1] + ao[3] > ao[0] + ao[2]
|
||||||
|
|
||||||
|
v0 = to_uint8(x , y , z, voxel_id, 4, ao[0], flip_id)
|
||||||
|
v1 = to_uint8(x , y + 1, z, voxel_id, 4, ao[1], flip_id)
|
||||||
|
v2 = to_uint8(x + 1, y + 1, z, voxel_id, 4, ao[2], flip_id)
|
||||||
|
v3 = to_uint8(x + 1, y , z, voxel_id, 4, ao[3], flip_id)
|
||||||
|
|
||||||
|
if flip_id:
|
||||||
|
index = add_data(vertex_data, index, v3, v0, v1, v3, v1, v2)
|
||||||
|
|
||||||
|
else:
|
||||||
|
index = add_data(vertex_data, index, v0, v1, v2, v0, v2, v3)
|
||||||
|
|
||||||
|
# front face
|
||||||
|
if is_void((x, y, z + 1), (wx, wy, wz + 1), world_voxels):
|
||||||
|
ao = get_ao((x, y, z + 1), (wx, wy, wz + 1), world_voxels, plane="Z")
|
||||||
|
flip_id = ao[1] + ao[3] > ao[0] + ao[2]
|
||||||
|
|
||||||
|
v0 = to_uint8(x , y , z + 1, voxel_id, 5, ao[0], flip_id)
|
||||||
|
v1 = to_uint8(x , y + 1, z + 1, voxel_id, 5, ao[1], flip_id)
|
||||||
|
v2 = to_uint8(x + 1, y + 1, z + 1, voxel_id, 5, ao[2], flip_id)
|
||||||
|
v3 = to_uint8(x + 1, y , z + 1, voxel_id, 5, ao[3], flip_id)
|
||||||
|
|
||||||
|
if flip_id:
|
||||||
|
index = add_data(vertex_data, index, v3, v1, v0, v3, v2, v1)
|
||||||
|
|
||||||
|
else:
|
||||||
|
index = add_data(vertex_data, index, v0, v2, v1, v0, v3, v2)
|
||||||
|
|
||||||
|
return vertex_data[:index + 1]
|
31
mesh/quad.py
Normal file
31
mesh/quad.py
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
from mesh.base import BaseMesh
|
||||||
|
|
||||||
|
|
||||||
|
class QuadMesh(BaseMesh):
|
||||||
|
def __init__(self, app):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.app = app
|
||||||
|
self.ctx = app.ctx
|
||||||
|
self.program = app.shader_program.quad
|
||||||
|
|
||||||
|
self.vbo_format = "3f 3f"
|
||||||
|
self.attributes = ("in_position", "in_color")
|
||||||
|
self.vao = self.get_vao()
|
||||||
|
|
||||||
|
def get_vertex_data(self):
|
||||||
|
vertices = [
|
||||||
|
(0.5, 0.5, 0.0), (-0.5, 0.5, 0.0), (-0.5, -0.5, 0.0),
|
||||||
|
(0.5, 0.5, 0.0), (-0.5, -0.5, 0.0), (0.5, -0.5, 0.0)
|
||||||
|
]
|
||||||
|
|
||||||
|
colors = [
|
||||||
|
(0, 1, 0), (1, 0, 0), (1, 1, 0),
|
||||||
|
(0, 1, 0), (1, 1, 0), (0, 0, 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
vertex_data = numpy.hstack([vertices, colors], dtype="float32")
|
||||||
|
return vertex_data
|
47
player.py
Normal file
47
player.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import pygame as pg
|
||||||
|
from camera import Camera
|
||||||
|
from settings import *
|
||||||
|
|
||||||
|
|
||||||
|
class Player(Camera):
|
||||||
|
def __init__(self, app, position=PLAYER_POS, yaw=-90, pitch=0):
|
||||||
|
self.app = app
|
||||||
|
super().__init__(position, yaw, pitch)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.keyboard_control()
|
||||||
|
self.mouse_control()
|
||||||
|
super().update()
|
||||||
|
|
||||||
|
def mouse_control(self):
|
||||||
|
mouse_dx, mouse_dy = pg.mouse.get_rel()
|
||||||
|
|
||||||
|
if mouse_dx:
|
||||||
|
self.rotate_yaw(delta_x=mouse_dx * MOUSE_SENSITIVITY)
|
||||||
|
|
||||||
|
if mouse_dy:
|
||||||
|
self.rotate_pitch(delta_y=mouse_dy * MOUSE_SENSITIVITY)
|
||||||
|
|
||||||
|
def keyboard_control(self):
|
||||||
|
key_state = pg.key.get_pressed()
|
||||||
|
vel = PLAYER_SPEED * self.app.delta_time
|
||||||
|
|
||||||
|
if key_state[pg.K_w]:
|
||||||
|
self.move_forward(vel)
|
||||||
|
|
||||||
|
if key_state[pg.K_s]:
|
||||||
|
self.move_back(vel)
|
||||||
|
|
||||||
|
if key_state[pg.K_d]:
|
||||||
|
self.move_right(vel)
|
||||||
|
|
||||||
|
if key_state[pg.K_a]:
|
||||||
|
self.move_left(vel)
|
||||||
|
|
||||||
|
if key_state[pg.K_q]:
|
||||||
|
self.move_up(vel)
|
||||||
|
|
||||||
|
if key_state[pg.K_e]:
|
||||||
|
self.move_down(vel)
|
16
scene.py
Normal file
16
scene.py
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
from world import World
|
||||||
|
|
||||||
|
|
||||||
|
class Scene:
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.world = World(self.app)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.world.update()
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
self.world.render()
|
46
settings.py
Normal file
46
settings.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from numba import njit
|
||||||
|
import numpy
|
||||||
|
import glm
|
||||||
|
import math
|
||||||
|
|
||||||
|
# resolution
|
||||||
|
WIN_RES = glm.vec2(1600, 1000)
|
||||||
|
|
||||||
|
# chunk
|
||||||
|
|
||||||
|
CHUNK_SIZE = 32
|
||||||
|
H_CHUNK_SIZE = CHUNK_SIZE // 2
|
||||||
|
CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE
|
||||||
|
CHUNK_VOL = CHUNK_AREA * CHUNK_SIZE
|
||||||
|
|
||||||
|
# world
|
||||||
|
WORLD_W, WORLD_H = 20, 3
|
||||||
|
WORLD_D = WORLD_W
|
||||||
|
WORLD_AREA = WORLD_W * WORLD_D
|
||||||
|
WORLD_VOL = WORLD_AREA * WORLD_H
|
||||||
|
|
||||||
|
# world center
|
||||||
|
|
||||||
|
CENTER_XZ = WORLD_W * H_CHUNK_SIZE
|
||||||
|
CENTER_Y = WORLD_H * H_CHUNK_SIZE
|
||||||
|
|
||||||
|
# camera
|
||||||
|
ASPECT_RATIO = WIN_RES.x / WIN_RES.y
|
||||||
|
FOV_DEG = 50
|
||||||
|
V_FOV = glm.radians(FOV_DEG) # vertical fov
|
||||||
|
H_FOV = 2 * math.atan(math.tan(V_FOV * 0.5) * ASPECT_RATIO)
|
||||||
|
NEAR = 0.1
|
||||||
|
FAR = 2000.0
|
||||||
|
PITCH_MAX = glm.radians(89)
|
||||||
|
|
||||||
|
# player
|
||||||
|
PLAYER_SPEED = 0.055
|
||||||
|
PLAYER_ROT_SPEED = 0.003
|
||||||
|
PLAYER_POS = glm.vec3(CENTER_XZ, WORLD_H * CHUNK_SIZE, CENTER_XZ)
|
||||||
|
MOUSE_SENSITIVITY = 0.002
|
||||||
|
|
||||||
|
# colors
|
||||||
|
|
||||||
|
BG_COLOR = glm.vec3(0.1, 0.16, 0.25)
|
32
shader_program.py
Normal file
32
shader_program.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
|
||||||
|
|
||||||
|
class ShaderProgram:
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.ctx = app.ctx
|
||||||
|
self.player = app.player
|
||||||
|
# -------- shaders -------- #
|
||||||
|
self.chunk = self.get_program(shader_name="chunk")
|
||||||
|
# ------------------------- #
|
||||||
|
self.set_uniforms_on_init()
|
||||||
|
self.chunk["u_texture_0"] = 0
|
||||||
|
|
||||||
|
def set_uniforms_on_init(self):
|
||||||
|
self.chunk["m_proj"].write(self.player.m_proj)
|
||||||
|
self.chunk["m_model"].write(glm.mat4())
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
self.chunk["m_view"].write(self.player.m_view)
|
||||||
|
|
||||||
|
def get_program(self, shader_name):
|
||||||
|
with open(f"shaders/{shader_name}.vert") as file:
|
||||||
|
vertex_shader = file.read()
|
||||||
|
|
||||||
|
with open(f"shaders/{shader_name}.frag") as file:
|
||||||
|
fragment_shader = file.read()
|
||||||
|
|
||||||
|
program = self.ctx.program(vertex_shader=vertex_shader, fragment_shader=fragment_shader)
|
||||||
|
return program
|
25
shaders/chunk.frag
Normal file
25
shaders/chunk.frag
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
const vec3 gamma = vec3(2.2);
|
||||||
|
const vec3 inv_gamma = 1 / gamma;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture_0;
|
||||||
|
|
||||||
|
in vec3 voxel_color;
|
||||||
|
in vec2 uv;
|
||||||
|
in float shading;
|
||||||
|
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 tex_col = texture(u_texture_0, uv).rgb;
|
||||||
|
tex_col = pow(tex_col, gamma);
|
||||||
|
|
||||||
|
tex_col.rgb *= voxel_color;
|
||||||
|
//tex_col = tex_col * 0.001 + vec3(1);
|
||||||
|
tex_col *= shading;
|
||||||
|
|
||||||
|
tex_col = pow(tex_col, inv_gamma);
|
||||||
|
fragColor = vec4(tex_col, 1);
|
||||||
|
}
|
51
shaders/chunk.vert
Normal file
51
shaders/chunk.vert
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
layout (location = 0) in ivec3 in_position;
|
||||||
|
layout (location = 1) in int voxel_id;
|
||||||
|
layout (location = 2) in int face_id;
|
||||||
|
layout (location = 3) in int ao_id;
|
||||||
|
layout (location = 4) in int flip_id;
|
||||||
|
|
||||||
|
uniform mat4 m_proj;
|
||||||
|
uniform mat4 m_view;
|
||||||
|
uniform mat4 m_model;
|
||||||
|
|
||||||
|
out vec3 voxel_color;
|
||||||
|
out vec2 uv;
|
||||||
|
out float shading;
|
||||||
|
|
||||||
|
const float ao_values[4] = float[4](0.1, 0.25, 0.1, 1.0);
|
||||||
|
|
||||||
|
const float face_shading[6] = float[6](
|
||||||
|
1.0, 0.5, // top bottom
|
||||||
|
0.5, 0.8, // right left
|
||||||
|
0.5, 0.8 // front back
|
||||||
|
);
|
||||||
|
|
||||||
|
const vec2 uv_coords[4] = vec2[4](
|
||||||
|
vec2(0, 0), vec2(0, 1),
|
||||||
|
vec2(1, 0), vec2(1, 1)
|
||||||
|
);
|
||||||
|
|
||||||
|
const int uv_indices[24] = int[24](
|
||||||
|
1, 0, 2, 1, 2, 3, // tex coord indices for vertices of an even face
|
||||||
|
3, 0, 2, 3, 1, 0, // odd face
|
||||||
|
3, 1, 0, 3, 0, 2, // even flipped face
|
||||||
|
1, 2, 3, 1, 0, 2 // odd flipped face
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
vec3 hash31(float p) {
|
||||||
|
vec3 p3 = fract(vec3(p * 21.2) * vec3(0.1031, 0.1030, 0.0973));
|
||||||
|
p3 += dot(p3, p3.yzx + 33.33);
|
||||||
|
return fract((p3.xxy + p3.yzz) * p3.zyx) + 0.05;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
int uv_index = gl_VertexID % 6 + ((face_id & 1) + flip_id * 2) * 6;
|
||||||
|
uv = uv_coords[uv_indices[uv_index]];
|
||||||
|
voxel_color = hash31(voxel_id);
|
||||||
|
shading = face_shading[face_id] * ao_values[ao_id];
|
||||||
|
gl_Position = m_proj * m_view * m_model * vec4(in_position, 1.0);
|
||||||
|
}
|
10
shaders/quad.frag
Normal file
10
shaders/quad.frag
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
layout (location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
in vec3 color;
|
||||||
|
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = vec4(color, 1.0);
|
||||||
|
}
|
16
shaders/quad.vert
Normal file
16
shaders/quad.vert
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#version 330 core
|
||||||
|
|
||||||
|
layout (location = 0) in vec3 in_position;
|
||||||
|
layout (location = 1) in vec3 in_color;
|
||||||
|
|
||||||
|
uniform mat4 m_proj;
|
||||||
|
uniform mat4 m_view;
|
||||||
|
uniform mat4 m_model;
|
||||||
|
|
||||||
|
out vec3 color;
|
||||||
|
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
color = in_color;
|
||||||
|
gl_Position = m_proj * m_view * m_model * vec4(in_position, 1.0);
|
||||||
|
}
|
32
textures.py
Normal file
32
textures.py
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
import pygame as pg
|
||||||
|
import moderngl as mgl
|
||||||
|
|
||||||
|
|
||||||
|
class Textures:
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.ctx = app.ctx
|
||||||
|
|
||||||
|
# load textures
|
||||||
|
#self.texture_0 = self.load("frame.png")
|
||||||
|
self.texture_0 = self.load("test.png")
|
||||||
|
|
||||||
|
# assign texture unit
|
||||||
|
self.texture_0.use(location=0)
|
||||||
|
|
||||||
|
def load(self, file_name):
|
||||||
|
texture = pg.image.load(f"assets/{file_name}")
|
||||||
|
texture = pg.transform.flip(texture, flip_x=True, flip_y=False)
|
||||||
|
|
||||||
|
texture = self.ctx.texture(
|
||||||
|
size=texture.get_size(),
|
||||||
|
components=4,
|
||||||
|
data=pg.image.tostring(texture, "RGBA", False)
|
||||||
|
)
|
||||||
|
|
||||||
|
texture.anisotropy = 32.0
|
||||||
|
texture.build_mipmaps()
|
||||||
|
texture.filter = (mgl.NEAREST, mgl.NEAREST)
|
||||||
|
return texture
|
39
world.py
Normal file
39
world.py
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
from world_objects.chunk import Chunk
|
||||||
|
|
||||||
|
|
||||||
|
class World:
|
||||||
|
def __init__(self, app):
|
||||||
|
self.app = app
|
||||||
|
self.chunks = [None for _ in range(WORLD_VOL)]
|
||||||
|
self.voxels = numpy.empty([WORLD_VOL, CHUNK_VOL], dtype="uint8")
|
||||||
|
self.build_chunks()
|
||||||
|
self.build_chunk_mesh()
|
||||||
|
|
||||||
|
def build_chunks(self):
|
||||||
|
for x in range(WORLD_W):
|
||||||
|
for y in range(WORLD_H):
|
||||||
|
for z in range(WORLD_D):
|
||||||
|
chunk = Chunk(self, position=(x, y, z))
|
||||||
|
|
||||||
|
chunk_index = x + WORLD_W * z + WORLD_AREA * y
|
||||||
|
self.chunks[chunk_index] = chunk
|
||||||
|
|
||||||
|
# put the chunk voxels in a separate array
|
||||||
|
self.voxels[chunk_index] = chunk.build_voxels()
|
||||||
|
|
||||||
|
# get pointer to voxels
|
||||||
|
chunk.voxels = self.voxels[chunk_index]
|
||||||
|
|
||||||
|
def build_chunk_mesh(self):
|
||||||
|
for chunk in self.chunks:
|
||||||
|
chunk.build_mesh()
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
for chunk in self.chunks:
|
||||||
|
chunk.render()
|
54
world_objects/chunk.py
Normal file
54
world_objects/chunk.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#!/usr/bin/python3
|
||||||
|
|
||||||
|
from settings import *
|
||||||
|
from mesh.chunk import ChunkMesh
|
||||||
|
|
||||||
|
|
||||||
|
class Chunk:
|
||||||
|
def __init__(self, world, position):
|
||||||
|
self.app = world.app
|
||||||
|
self.world = world
|
||||||
|
self.position = position
|
||||||
|
self.m_model = self.get_model_matrix()
|
||||||
|
self.voxels: numpy.array = None
|
||||||
|
self.mesh: ChunkMesh = None
|
||||||
|
self.is_empty = True
|
||||||
|
|
||||||
|
def get_model_matrix(self):
|
||||||
|
m_model = glm.translate(glm.mat4(), glm.vec3(self.position) * CHUNK_SIZE)
|
||||||
|
return m_model
|
||||||
|
|
||||||
|
def set_uniform(self):
|
||||||
|
self.mesh.program["m_model"].write(self.m_model)
|
||||||
|
|
||||||
|
def build_mesh(self):
|
||||||
|
self.mesh = ChunkMesh(self)
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
self.set_uniform()
|
||||||
|
self.mesh.render()
|
||||||
|
|
||||||
|
def build_voxels(self):
|
||||||
|
# empty chunk
|
||||||
|
voxels = numpy.zeros(CHUNK_VOL, dtype="uint8")
|
||||||
|
|
||||||
|
# fill chunk
|
||||||
|
cx, cy, cz = glm.ivec3(self.position) * CHUNK_SIZE
|
||||||
|
|
||||||
|
# fill chunk
|
||||||
|
for x in range(CHUNK_SIZE):
|
||||||
|
for z in range(CHUNK_SIZE):
|
||||||
|
wx = x + cx
|
||||||
|
wz = z + cz
|
||||||
|
world_height = int(glm.simplex(glm.vec2(wx, wz) * 0.01) * 32 + 32)
|
||||||
|
local_height = min(world_height - cy, CHUNK_SIZE)
|
||||||
|
|
||||||
|
for y in range(local_height):
|
||||||
|
wy = y + cy
|
||||||
|
|
||||||
|
voxels[x + CHUNK_SIZE * z + CHUNK_AREA * y] = wy + 1
|
||||||
|
|
||||||
|
if numpy.any(voxels):
|
||||||
|
self.is_empty = False
|
||||||
|
|
||||||
|
return voxels
|
Loading…
Reference in a new issue