Minecraft3193092
commited on
Commit
•
f92c150
1
Parent(s):
55c258d
Upload 15 files
Browse files- block_type.py +71 -0
- chunk.py +183 -0
- collider.py +67 -0
- entity.py +161 -0
- frag.glsl +18 -0
- hit.py +105 -0
- main.py +204 -0
- matrix.py +139 -0
- player.py +78 -0
- save.py +99 -0
- shader.py +71 -0
- subchunk.py +99 -0
- texture_manager.py +42 -0
- vert.glsl +18 -0
- world.py +176 -0
block_type.py
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import collider
|
2 |
+
|
3 |
+
import models.cube # default model
|
4 |
+
|
5 |
+
class Block_type:
|
6 |
+
# new optional model argument (cube model by default)
|
7 |
+
def __init__(self, texture_manager, name = "unknown", block_face_textures = {"all": "cobblestone"}, model = models.cube):
|
8 |
+
self.name = name
|
9 |
+
self.block_face_textures = block_face_textures
|
10 |
+
self.model = model
|
11 |
+
|
12 |
+
# create members based on model attributes
|
13 |
+
|
14 |
+
self.transparent = model.transparent
|
15 |
+
self.is_cube = model.is_cube
|
16 |
+
self.glass = model.glass
|
17 |
+
|
18 |
+
# create colliders
|
19 |
+
|
20 |
+
self.colliders = []
|
21 |
+
|
22 |
+
for _collider in model.colliders:
|
23 |
+
self.colliders.append(collider.Collider(*_collider))
|
24 |
+
|
25 |
+
# replace data contained in numbers.py with model specific data
|
26 |
+
|
27 |
+
self.vertex_positions = model.vertex_positions
|
28 |
+
self.tex_coords = model.tex_coords.copy()
|
29 |
+
self.shading_values = model.shading_values
|
30 |
+
|
31 |
+
def set_block_face(face, texture):
|
32 |
+
# make sure we don't add inexistent faces
|
33 |
+
|
34 |
+
if face > len(self.tex_coords) - 1:
|
35 |
+
return
|
36 |
+
|
37 |
+
self.tex_coords[face] = self.tex_coords[face].copy()
|
38 |
+
|
39 |
+
for vertex in range(4):
|
40 |
+
self.tex_coords[face][vertex * 3 + 2] = texture
|
41 |
+
|
42 |
+
for face in block_face_textures:
|
43 |
+
texture = block_face_textures[face]
|
44 |
+
texture_manager.add_texture(texture)
|
45 |
+
|
46 |
+
texture_index = texture_manager.textures.index(texture)
|
47 |
+
|
48 |
+
if face == "all":
|
49 |
+
for i in range(len(self.tex_coords)):
|
50 |
+
set_block_face(i, texture_index)
|
51 |
+
|
52 |
+
elif face == "sides":
|
53 |
+
set_block_face(0, texture_index)
|
54 |
+
set_block_face(1, texture_index)
|
55 |
+
set_block_face(4, texture_index)
|
56 |
+
set_block_face(5, texture_index)
|
57 |
+
|
58 |
+
elif face == "x":
|
59 |
+
set_block_face(0, texture_index)
|
60 |
+
set_block_face(1, texture_index)
|
61 |
+
|
62 |
+
elif face == "y":
|
63 |
+
set_block_face(2, texture_index)
|
64 |
+
set_block_face(3, texture_index)
|
65 |
+
|
66 |
+
elif face == "z":
|
67 |
+
set_block_face(4, texture_index)
|
68 |
+
set_block_face(5, texture_index)
|
69 |
+
|
70 |
+
else:
|
71 |
+
set_block_face(["right", "left", "top", "bottom", "front", "back"].index(face), texture_index)
|
chunk.py
ADDED
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ctypes
|
2 |
+
import math
|
3 |
+
|
4 |
+
import pyglet.gl as gl
|
5 |
+
|
6 |
+
import subchunk
|
7 |
+
|
8 |
+
CHUNK_WIDTH = 16
|
9 |
+
CHUNK_HEIGHT = 128
|
10 |
+
CHUNK_LENGTH = 16
|
11 |
+
|
12 |
+
class Chunk:
|
13 |
+
def __init__(self, world, chunk_position):
|
14 |
+
self.world = world
|
15 |
+
|
16 |
+
self.modified = False
|
17 |
+
self.chunk_position = chunk_position
|
18 |
+
|
19 |
+
self.position = (
|
20 |
+
self.chunk_position[0] * CHUNK_WIDTH,
|
21 |
+
self.chunk_position[1] * CHUNK_HEIGHT,
|
22 |
+
self.chunk_position[2] * CHUNK_LENGTH)
|
23 |
+
|
24 |
+
self.blocks = [[[0
|
25 |
+
for z in range(CHUNK_LENGTH)]
|
26 |
+
for y in range(CHUNK_HEIGHT)]
|
27 |
+
for x in range(CHUNK_WIDTH )]
|
28 |
+
|
29 |
+
self.subchunks = {}
|
30 |
+
|
31 |
+
for x in range(int(CHUNK_WIDTH / subchunk.SUBCHUNK_WIDTH)):
|
32 |
+
for y in range(int(CHUNK_HEIGHT / subchunk.SUBCHUNK_HEIGHT)):
|
33 |
+
for z in range(int(CHUNK_LENGTH / subchunk.SUBCHUNK_LENGTH)):
|
34 |
+
self.subchunks[(x, y, z)] = subchunk.Subchunk(self, (x, y, z))
|
35 |
+
|
36 |
+
# mesh variables
|
37 |
+
|
38 |
+
self.mesh_vertex_positions = []
|
39 |
+
self.mesh_tex_coords = []
|
40 |
+
self.mesh_shading_values = []
|
41 |
+
|
42 |
+
self.mesh_index_counter = 0
|
43 |
+
self.mesh_indices = []
|
44 |
+
|
45 |
+
# create VAO and VBO's
|
46 |
+
|
47 |
+
self.vao = gl.GLuint(0)
|
48 |
+
gl.glGenVertexArrays(1, self.vao)
|
49 |
+
gl.glBindVertexArray(self.vao)
|
50 |
+
|
51 |
+
self.vertex_position_vbo = gl.GLuint(0)
|
52 |
+
gl.glGenBuffers(1, self.vertex_position_vbo)
|
53 |
+
|
54 |
+
self.tex_coord_vbo = gl.GLuint(0)
|
55 |
+
gl.glGenBuffers(1, self.tex_coord_vbo)
|
56 |
+
|
57 |
+
self.shading_values_vbo = gl.GLuint(0)
|
58 |
+
gl.glGenBuffers(1, self.shading_values_vbo)
|
59 |
+
|
60 |
+
self.ibo = gl.GLuint(0)
|
61 |
+
gl.glGenBuffers(1, self.ibo)
|
62 |
+
|
63 |
+
def update_subchunk_meshes(self):
|
64 |
+
for subchunk_position in self.subchunks:
|
65 |
+
subchunk = self.subchunks[subchunk_position]
|
66 |
+
subchunk.update_mesh()
|
67 |
+
|
68 |
+
def update_at_position(self, position):
|
69 |
+
x, y, z = position
|
70 |
+
|
71 |
+
lx = int(x % subchunk.SUBCHUNK_WIDTH )
|
72 |
+
ly = int(y % subchunk.SUBCHUNK_HEIGHT)
|
73 |
+
lz = int(z % subchunk.SUBCHUNK_LENGTH)
|
74 |
+
|
75 |
+
clx, cly, clz = self.world.get_local_position(position)
|
76 |
+
|
77 |
+
sx = math.floor(clx / subchunk.SUBCHUNK_WIDTH)
|
78 |
+
sy = math.floor(cly / subchunk.SUBCHUNK_HEIGHT)
|
79 |
+
sz = math.floor(clz / subchunk.SUBCHUNK_LENGTH)
|
80 |
+
|
81 |
+
self.subchunks[(sx, sy, sz)].update_mesh()
|
82 |
+
|
83 |
+
def try_update_subchunk_mesh(subchunk_position):
|
84 |
+
if subchunk_position in self.subchunks:
|
85 |
+
self.subchunks[subchunk_position].update_mesh()
|
86 |
+
|
87 |
+
if lx == subchunk.SUBCHUNK_WIDTH - 1: try_update_subchunk_mesh((sx + 1, sy, sz))
|
88 |
+
if lx == 0: try_update_subchunk_mesh((sx - 1, sy, sz))
|
89 |
+
|
90 |
+
if ly == subchunk.SUBCHUNK_HEIGHT - 1: try_update_subchunk_mesh((sx, sy + 1, sz))
|
91 |
+
if ly == 0: try_update_subchunk_mesh((sx, sy - 1, sz))
|
92 |
+
|
93 |
+
if lz == subchunk.SUBCHUNK_LENGTH - 1: try_update_subchunk_mesh((sx, sy, sz + 1))
|
94 |
+
if lz == 0: try_update_subchunk_mesh((sx, sy, sz - 1))
|
95 |
+
|
96 |
+
def update_mesh(self):
|
97 |
+
# combine all the small subchunk meshes into one big chunk mesh
|
98 |
+
|
99 |
+
self.mesh_vertex_positions = []
|
100 |
+
self.mesh_tex_coords = []
|
101 |
+
self.mesh_shading_values = []
|
102 |
+
|
103 |
+
self.mesh_index_counter = 0
|
104 |
+
self.mesh_indices = []
|
105 |
+
|
106 |
+
for subchunk_position in self.subchunks:
|
107 |
+
subchunk = self.subchunks[subchunk_position]
|
108 |
+
|
109 |
+
self.mesh_vertex_positions.extend(subchunk.mesh_vertex_positions)
|
110 |
+
self.mesh_tex_coords.extend(subchunk.mesh_tex_coords)
|
111 |
+
self.mesh_shading_values.extend(subchunk.mesh_shading_values)
|
112 |
+
|
113 |
+
mesh_indices = [index + self.mesh_index_counter for index in subchunk.mesh_indices]
|
114 |
+
|
115 |
+
self.mesh_indices.extend(mesh_indices)
|
116 |
+
self.mesh_index_counter += subchunk.mesh_index_counter
|
117 |
+
|
118 |
+
# send the full mesh data to the GPU and free the memory used client-side (we don't need it anymore)
|
119 |
+
# don't forget to save the length of 'self.mesh_indices' before freeing
|
120 |
+
|
121 |
+
self.mesh_indices_length = len(self.mesh_indices)
|
122 |
+
self.send_mesh_data_to_gpu()
|
123 |
+
|
124 |
+
del self.mesh_vertex_positions
|
125 |
+
del self.mesh_tex_coords
|
126 |
+
del self.mesh_shading_values
|
127 |
+
|
128 |
+
del self.mesh_indices
|
129 |
+
|
130 |
+
def send_mesh_data_to_gpu(self): # pass mesh data to gpu
|
131 |
+
if not self.mesh_index_counter:
|
132 |
+
return
|
133 |
+
|
134 |
+
gl.glBindVertexArray(self.vao)
|
135 |
+
|
136 |
+
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.vertex_position_vbo)
|
137 |
+
gl.glBufferData(
|
138 |
+
gl.GL_ARRAY_BUFFER,
|
139 |
+
ctypes.sizeof(gl.GLfloat * len(self.mesh_vertex_positions)),
|
140 |
+
(gl.GLfloat * len(self.mesh_vertex_positions)) (*self.mesh_vertex_positions),
|
141 |
+
gl.GL_STATIC_DRAW)
|
142 |
+
|
143 |
+
gl.glVertexAttribPointer(0, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, 0)
|
144 |
+
gl.glEnableVertexAttribArray(0)
|
145 |
+
|
146 |
+
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.tex_coord_vbo)
|
147 |
+
gl.glBufferData(
|
148 |
+
gl.GL_ARRAY_BUFFER,
|
149 |
+
ctypes.sizeof(gl.GLfloat * len(self.mesh_tex_coords)),
|
150 |
+
(gl.GLfloat * len(self.mesh_tex_coords)) (*self.mesh_tex_coords),
|
151 |
+
gl.GL_STATIC_DRAW)
|
152 |
+
|
153 |
+
gl.glVertexAttribPointer(1, 3, gl.GL_FLOAT, gl.GL_FALSE, 0, 0)
|
154 |
+
gl.glEnableVertexAttribArray(1)
|
155 |
+
|
156 |
+
gl.glBindBuffer(gl.GL_ARRAY_BUFFER, self.shading_values_vbo)
|
157 |
+
gl.glBufferData(
|
158 |
+
gl.GL_ARRAY_BUFFER,
|
159 |
+
ctypes.sizeof(gl.GLfloat * len(self.mesh_shading_values)),
|
160 |
+
(gl.GLfloat * len(self.mesh_shading_values)) (*self.mesh_shading_values),
|
161 |
+
gl.GL_STATIC_DRAW)
|
162 |
+
|
163 |
+
gl.glVertexAttribPointer(2, 1, gl.GL_FLOAT, gl.GL_FALSE, 0, 0)
|
164 |
+
gl.glEnableVertexAttribArray(2)
|
165 |
+
|
166 |
+
gl.glBindBuffer(gl.GL_ELEMENT_ARRAY_BUFFER, self.ibo)
|
167 |
+
gl.glBufferData(
|
168 |
+
gl.GL_ELEMENT_ARRAY_BUFFER,
|
169 |
+
ctypes.sizeof(gl.GLuint * self.mesh_indices_length),
|
170 |
+
(gl.GLuint * self.mesh_indices_length) (*self.mesh_indices),
|
171 |
+
gl.GL_STATIC_DRAW)
|
172 |
+
|
173 |
+
def draw(self):
|
174 |
+
if not self.mesh_index_counter:
|
175 |
+
return
|
176 |
+
|
177 |
+
gl.glBindVertexArray(self.vao)
|
178 |
+
|
179 |
+
gl.glDrawElements(
|
180 |
+
gl.GL_TRIANGLES,
|
181 |
+
self.mesh_indices_length,
|
182 |
+
gl.GL_UNSIGNED_INT,
|
183 |
+
None)
|
collider.py
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class Collider:
|
2 |
+
def __init__(self, pos1 = (None,) * 3, pos2 = (None,) * 3):
|
3 |
+
# pos1: position of the collider vertex in the -X, -Y, -Z direction
|
4 |
+
# pos2: position of the collider vertex in the +X, +Y, +Z direction
|
5 |
+
|
6 |
+
self.x1, self.y1, self.z1 = pos1
|
7 |
+
self.x2, self.y2, self.z2 = pos2
|
8 |
+
|
9 |
+
def __add__(self, pos):
|
10 |
+
x, y, z = pos
|
11 |
+
|
12 |
+
return Collider(
|
13 |
+
(self.x1 + x, self.y1 + y, self.z1 + z),
|
14 |
+
(self.x2 + x, self.y2 + y, self.z2 + z)
|
15 |
+
)
|
16 |
+
|
17 |
+
def __and__(self, collider):
|
18 |
+
x = min(self.x2, collider.x2) - max(self.x1, collider.x1)
|
19 |
+
y = min(self.y2, collider.y2) - max(self.y1, collider.y1)
|
20 |
+
z = min(self.z2, collider.z2) - max(self.z1, collider.z1)
|
21 |
+
|
22 |
+
return x > 0 and y > 0 and z > 0
|
23 |
+
|
24 |
+
def collide(self, collider, velocity):
|
25 |
+
# self: the dynamic collider, which moves
|
26 |
+
# collider: the static collider, which stays put
|
27 |
+
|
28 |
+
no_collision = 1, None
|
29 |
+
|
30 |
+
# find entry & exit times for each axis
|
31 |
+
|
32 |
+
vx, vy, vz = velocity
|
33 |
+
|
34 |
+
time = lambda x, y: x / y if y else float('-' * (x > 0) + "inf")
|
35 |
+
|
36 |
+
x_entry = time(collider.x1 - self.x2 if vx > 0 else collider.x2 - self.x1, vx)
|
37 |
+
x_exit = time(collider.x2 - self.x1 if vx > 0 else collider.x1 - self.x2, vx)
|
38 |
+
|
39 |
+
y_entry = time(collider.y1 - self.y2 if vy > 0 else collider.y2 - self.y1, vy)
|
40 |
+
y_exit = time(collider.y2 - self.y1 if vy > 0 else collider.y1 - self.y2, vy)
|
41 |
+
|
42 |
+
z_entry = time(collider.z1 - self.z2 if vz > 0 else collider.z2 - self.z1, vz)
|
43 |
+
z_exit = time(collider.z2 - self.z1 if vz > 0 else collider.z1 - self.z2, vz)
|
44 |
+
|
45 |
+
# make sure we actually got a collision
|
46 |
+
|
47 |
+
if x_entry < 0 and y_entry < 0 and z_entry < 0:
|
48 |
+
return no_collision
|
49 |
+
|
50 |
+
if x_entry > 1 or y_entry > 1 or z_entry > 1:
|
51 |
+
return no_collision
|
52 |
+
|
53 |
+
# on which axis did we collide first?
|
54 |
+
|
55 |
+
entry = max(x_entry, y_entry, z_entry)
|
56 |
+
exit_ = min(x_exit, y_exit, z_exit )
|
57 |
+
|
58 |
+
if entry > exit_:
|
59 |
+
return no_collision
|
60 |
+
|
61 |
+
# find normal of surface we collided with
|
62 |
+
|
63 |
+
nx = (0, -1 if vx > 0 else 1)[entry == x_entry]
|
64 |
+
ny = (0, -1 if vy > 0 else 1)[entry == y_entry]
|
65 |
+
nz = (0, -1 if vz > 0 else 1)[entry == z_entry]
|
66 |
+
|
67 |
+
return entry, (nx, ny, nz)
|
entity.py
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
import collider
|
3 |
+
|
4 |
+
FLYING_ACCEL = (0, 0, 0)
|
5 |
+
GRAVITY_ACCEL = (0, -32, 0)
|
6 |
+
|
7 |
+
# these values all come (losely) from Minecraft, but are multiplied by 20 (since Minecraft runs at 20 TPS)
|
8 |
+
|
9 |
+
FRICTION = ( 20, 20, 20)
|
10 |
+
|
11 |
+
DRAG_FLY = ( 5, 5, 5)
|
12 |
+
DRAG_JUMP = (1.8, 0, 1.8)
|
13 |
+
DRAG_FALL = (1.8, 0.4, 1.8)
|
14 |
+
|
15 |
+
class Entity:
|
16 |
+
def __init__(self, world):
|
17 |
+
self.world = world
|
18 |
+
|
19 |
+
# physical variables
|
20 |
+
|
21 |
+
self.jump_height = 1.25
|
22 |
+
self.flying = False
|
23 |
+
|
24 |
+
self.position = [0, 80, 0]
|
25 |
+
self.rotation = [-math.tau / 4, 0]
|
26 |
+
|
27 |
+
self.velocity = [0, 0, 0]
|
28 |
+
self.accel = [0, 0, 0]
|
29 |
+
|
30 |
+
# collision variables
|
31 |
+
|
32 |
+
self.width = 0.6
|
33 |
+
self.height = 1.8
|
34 |
+
|
35 |
+
self.collider = collider.Collider()
|
36 |
+
self.grounded = False
|
37 |
+
|
38 |
+
def update_collider(self):
|
39 |
+
x, y, z = self.position
|
40 |
+
|
41 |
+
self.collider.x1 = x - self.width / 2
|
42 |
+
self.collider.x2 = x + self.width / 2
|
43 |
+
|
44 |
+
self.collider.y1 = y
|
45 |
+
self.collider.y2 = y + self.height
|
46 |
+
|
47 |
+
self.collider.z1 = z - self.width / 2
|
48 |
+
self.collider.z2 = z + self.width / 2
|
49 |
+
|
50 |
+
def teleport(self, pos):
|
51 |
+
self.position = list(pos)
|
52 |
+
self.velocity = [0, 0, 0] # to prevent collisions
|
53 |
+
|
54 |
+
def jump(self, height = None):
|
55 |
+
# obviously, we can't initiate a jump while in mid-air
|
56 |
+
|
57 |
+
if not self.grounded:
|
58 |
+
return
|
59 |
+
|
60 |
+
if height is None:
|
61 |
+
height = self.jump_height
|
62 |
+
|
63 |
+
self.velocity[1] = math.sqrt(-2 * GRAVITY_ACCEL[1] * height)
|
64 |
+
|
65 |
+
@property
|
66 |
+
def friction(self):
|
67 |
+
if self.flying:
|
68 |
+
return DRAG_FLY
|
69 |
+
|
70 |
+
elif self.grounded:
|
71 |
+
return FRICTION
|
72 |
+
|
73 |
+
elif self.velocity[1] > 0:
|
74 |
+
return DRAG_JUMP
|
75 |
+
|
76 |
+
return DRAG_FALL
|
77 |
+
|
78 |
+
def update(self, delta_time):
|
79 |
+
# apply input acceleration, and adjust for friction/drag
|
80 |
+
|
81 |
+
self.velocity = [v + a * f * delta_time for v, a, f in zip(self.velocity, self.accel, self.friction)]
|
82 |
+
self.accel = [0, 0, 0]
|
83 |
+
|
84 |
+
# compute collisions
|
85 |
+
|
86 |
+
self.update_collider()
|
87 |
+
self.grounded = False
|
88 |
+
|
89 |
+
for _ in range(3):
|
90 |
+
adjusted_velocity = [v * delta_time for v in self.velocity]
|
91 |
+
vx, vy, vz = adjusted_velocity
|
92 |
+
|
93 |
+
# find all the blocks we could potentially be colliding with
|
94 |
+
# this step is known as "broad-phasing"
|
95 |
+
|
96 |
+
step_x = 1 if vx > 0 else -1
|
97 |
+
step_y = 1 if vy > 0 else -1
|
98 |
+
step_z = 1 if vz > 0 else -1
|
99 |
+
|
100 |
+
steps_xz = int(self.width / 2)
|
101 |
+
steps_y = int(self.height)
|
102 |
+
|
103 |
+
x, y, z = map(int, self.position)
|
104 |
+
cx, cy, cz = [int(x + v) for x, v in zip(self.position, adjusted_velocity)]
|
105 |
+
|
106 |
+
potential_collisions = []
|
107 |
+
|
108 |
+
for i in range(x - step_x * (steps_xz + 1), cx + step_x * (steps_xz + 2), step_x):
|
109 |
+
for j in range(y - step_y * (steps_y + 2), cy + step_y * (steps_y + 3), step_y):
|
110 |
+
for k in range(z - step_z * (steps_xz + 1), cz + step_z * (steps_xz + 2), step_z):
|
111 |
+
pos = (i, j, k)
|
112 |
+
num = self.world.get_block_number(pos)
|
113 |
+
|
114 |
+
if not num:
|
115 |
+
continue
|
116 |
+
|
117 |
+
for _collider in self.world.block_types[num].colliders:
|
118 |
+
entry_time, normal = self.collider.collide(_collider + pos, adjusted_velocity)
|
119 |
+
|
120 |
+
if normal is None:
|
121 |
+
continue
|
122 |
+
|
123 |
+
potential_collisions.append((entry_time, normal))
|
124 |
+
|
125 |
+
# get first collision
|
126 |
+
|
127 |
+
if not potential_collisions:
|
128 |
+
break
|
129 |
+
|
130 |
+
entry_time, normal = min(potential_collisions, key = lambda x: x[0])
|
131 |
+
entry_time -= 0.001
|
132 |
+
|
133 |
+
if normal[0]:
|
134 |
+
self.velocity[0] = 0
|
135 |
+
self.position[0] += vx * entry_time
|
136 |
+
|
137 |
+
if normal[1]:
|
138 |
+
self.velocity[1] = 0
|
139 |
+
self.position[1] += vy * entry_time
|
140 |
+
|
141 |
+
if normal[2]:
|
142 |
+
self.velocity[2] = 0
|
143 |
+
self.position[2] += vz * entry_time
|
144 |
+
|
145 |
+
if normal[1] == 1:
|
146 |
+
self.grounded = True
|
147 |
+
|
148 |
+
self.position = [x + v * delta_time for x, v in zip(self.position, self.velocity)]
|
149 |
+
|
150 |
+
# apply gravity acceleration
|
151 |
+
|
152 |
+
gravity = FLYING_ACCEL if self.flying else GRAVITY_ACCEL
|
153 |
+
self.velocity = [v + a * delta_time for v, a in zip(self.velocity, gravity)]
|
154 |
+
|
155 |
+
# apply friction/drag
|
156 |
+
|
157 |
+
self.velocity = [v - min(v * f * delta_time, v, key = abs) for v, f in zip(self.velocity, self.friction)]
|
158 |
+
|
159 |
+
# make sure we can rely on the entity's collider outside of this function
|
160 |
+
|
161 |
+
self.update_collider()
|
frag.glsl
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#version 330
|
2 |
+
|
3 |
+
out vec4 fragment_colour;
|
4 |
+
|
5 |
+
uniform sampler2DArray texture_array_sampler;
|
6 |
+
|
7 |
+
in vec3 local_position;
|
8 |
+
in vec3 interpolated_tex_coords;
|
9 |
+
in float interpolated_shading_value;
|
10 |
+
|
11 |
+
void main(void) {
|
12 |
+
vec4 texture_colour = texture(texture_array_sampler, interpolated_tex_coords);
|
13 |
+
fragment_colour = texture_colour * interpolated_shading_value;
|
14 |
+
|
15 |
+
if (texture_colour.a == 0.0) { // discard if texel's alpha component is 0 (texel is transparent)
|
16 |
+
discard;
|
17 |
+
}
|
18 |
+
}
|
hit.py
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
|
3 |
+
HIT_RANGE = 3
|
4 |
+
|
5 |
+
class Hit_ray:
|
6 |
+
def __init__(self, world, rotation, starting_position):
|
7 |
+
self.world = world
|
8 |
+
|
9 |
+
# get the ray unit vector based on rotation angles
|
10 |
+
# sqrt(ux ^ 2 + uy ^ 2 + uz ^ 2) must always equal 1
|
11 |
+
|
12 |
+
self.vector = (
|
13 |
+
math.cos(rotation[0]) * math.cos(rotation[1]),
|
14 |
+
math.sin(rotation[1]),
|
15 |
+
math.sin(rotation[0]) * math.cos(rotation[1]))
|
16 |
+
|
17 |
+
# point position
|
18 |
+
self.position = list(starting_position)
|
19 |
+
|
20 |
+
# block position in which point currently is
|
21 |
+
self.block = tuple(map(lambda x: int(round(x)), self.position))
|
22 |
+
|
23 |
+
# current distance the point has travelled
|
24 |
+
self.distance = 0
|
25 |
+
|
26 |
+
# 'check' and 'step' both return 'True' if something is hit, and 'False' if not
|
27 |
+
|
28 |
+
def check(self, hit_callback, distance, current_block, next_block):
|
29 |
+
if self.world.get_block_number(next_block):
|
30 |
+
hit_callback(current_block, next_block)
|
31 |
+
return True
|
32 |
+
|
33 |
+
else:
|
34 |
+
self.position = list(map(lambda x: self.position[x] + self.vector[x] * distance, range(3)))
|
35 |
+
|
36 |
+
self.block = next_block
|
37 |
+
self.distance += distance
|
38 |
+
|
39 |
+
return False
|
40 |
+
|
41 |
+
def step(self, hit_callback):
|
42 |
+
bx, by, bz = self.block
|
43 |
+
|
44 |
+
# point position relative to block centre
|
45 |
+
local_position = list(map(lambda x: self.position[x] - self.block[x], range(3)))
|
46 |
+
|
47 |
+
# we don't want to deal with negatives, so remove the sign
|
48 |
+
# this is also cool because it means we don't need to take into account the sign of our ray vector
|
49 |
+
# we do need to remember which components were negative for later on, however
|
50 |
+
|
51 |
+
sign = [1, 1, 1] # '1' for positive, '-1' for negative
|
52 |
+
absolute_vector = list(self.vector)
|
53 |
+
|
54 |
+
for component in range(3):
|
55 |
+
if self.vector[component] < 0:
|
56 |
+
sign[component] = -1
|
57 |
+
|
58 |
+
absolute_vector[component] = -absolute_vector[component]
|
59 |
+
local_position[component] = -local_position[component]
|
60 |
+
|
61 |
+
lx, ly, lz = local_position
|
62 |
+
vx, vy, vz = absolute_vector
|
63 |
+
|
64 |
+
# calculate intersections
|
65 |
+
# I only detail the math for the first component (X) because the rest is pretty self-explanatory
|
66 |
+
|
67 |
+
# ray line (passing through the point) r ≡ (x - lx) / vx = (y - ly) / lz = (z - lz) / vz (parametric equation)
|
68 |
+
|
69 |
+
# +x face fx ≡ x = 0.5 (y & z can be any real number)
|
70 |
+
# r ∩ fx ≡ (0.5 - lx) / vx = (y - ly) / vy = (z - lz) / vz
|
71 |
+
|
72 |
+
# x: x = 0.5
|
73 |
+
# y: (y - ly) / vy = (0.5 - lx) / vx IFF y = (0.5 - lx) / vx * vy + ly
|
74 |
+
# z: (z - lz) / vz = (0.5 - lx) / vx IFF z = (0.5 - lx) / vx * vz + lz
|
75 |
+
|
76 |
+
if vx:
|
77 |
+
x = 0.5
|
78 |
+
y = (0.5 - lx) / vx * vy + ly
|
79 |
+
z = (0.5 - lx) / vx * vz + lz
|
80 |
+
|
81 |
+
if y >= -0.5 and y <= 0.5 and z >= -0.5 and z <= 0.5:
|
82 |
+
distance = math.sqrt((x - lx) ** 2 + (y - ly) ** 2 + (z - lz) ** 2)
|
83 |
+
|
84 |
+
# we can return straight away here
|
85 |
+
# if we intersect with one face, we know for a fact we're not intersecting with any of the others
|
86 |
+
|
87 |
+
return self.check(hit_callback, distance, (bx, by, bz), (bx + sign[0], by, bz))
|
88 |
+
|
89 |
+
if vy:
|
90 |
+
x = (0.5 - ly) / vy * vx + lx
|
91 |
+
y = 0.5
|
92 |
+
z = (0.5 - ly) / vy * vz + lz
|
93 |
+
|
94 |
+
if x >= -0.5 and x <= 0.5 and z >= -0.5 and z <= 0.5:
|
95 |
+
distance = math.sqrt((x - lx) ** 2 + (y - ly) ** 2 + (z - lz) ** 2)
|
96 |
+
return self.check(hit_callback, distance, (bx, by, bz), (bx, by + sign[1], bz))
|
97 |
+
|
98 |
+
if vz:
|
99 |
+
x = (0.5 - lz) / vz * vx + lx
|
100 |
+
y = (0.5 - lz) / vz * vy + ly
|
101 |
+
z = 0.5
|
102 |
+
|
103 |
+
if x >= -0.5 and x <= 0.5 and y >= -0.5 and y <= 0.5:
|
104 |
+
distance = math.sqrt((x - lx) ** 2 + (y - ly) ** 2 + (z - lz) ** 2)
|
105 |
+
return self.check(hit_callback, distance, (bx, by, bz), (bx, by, bz + sign[2]))
|
main.py
ADDED
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
import ctypes
|
3 |
+
import random
|
4 |
+
import pyglet
|
5 |
+
|
6 |
+
pyglet.options["shadow_window"] = False
|
7 |
+
pyglet.options["debug_gl"] = False
|
8 |
+
|
9 |
+
import pyglet.gl as gl
|
10 |
+
|
11 |
+
import matrix
|
12 |
+
import shader
|
13 |
+
import player
|
14 |
+
|
15 |
+
import block_type
|
16 |
+
import texture_manager
|
17 |
+
|
18 |
+
import chunk
|
19 |
+
import world
|
20 |
+
|
21 |
+
import hit
|
22 |
+
|
23 |
+
class Window(pyglet.window.Window):
|
24 |
+
def __init__(self, **args):
|
25 |
+
super().__init__(**args)
|
26 |
+
|
27 |
+
# create world
|
28 |
+
|
29 |
+
self.world = world.World()
|
30 |
+
|
31 |
+
# create shader
|
32 |
+
|
33 |
+
self.shader = shader.Shader("vert.glsl", "frag.glsl")
|
34 |
+
self.shader_sampler_location = self.shader.find_uniform(b"texture_array_sampler")
|
35 |
+
self.shader.use()
|
36 |
+
|
37 |
+
# pyglet stuff
|
38 |
+
|
39 |
+
pyglet.clock.schedule_interval(self.update, 1.0 / 60)
|
40 |
+
self.mouse_captured = False
|
41 |
+
|
42 |
+
# player stuff
|
43 |
+
|
44 |
+
self.player = player.Player(self.world, self.shader, self.width, self.height)
|
45 |
+
|
46 |
+
# misc stuff
|
47 |
+
|
48 |
+
self.holding = 44 # 5
|
49 |
+
|
50 |
+
def update(self, delta_time):
|
51 |
+
# print(f"FPS: {1.0 / delta_time}")
|
52 |
+
|
53 |
+
if not self.mouse_captured:
|
54 |
+
self.player.input = [0, 0, 0]
|
55 |
+
|
56 |
+
self.player.update(delta_time)
|
57 |
+
|
58 |
+
def on_draw(self):
|
59 |
+
self.player.update_matrices()
|
60 |
+
|
61 |
+
# bind textures
|
62 |
+
|
63 |
+
gl.glActiveTexture(gl.GL_TEXTURE0)
|
64 |
+
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, self.world.texture_manager.texture_array)
|
65 |
+
gl.glUniform1i(self.shader_sampler_location, 0)
|
66 |
+
|
67 |
+
# draw stuff
|
68 |
+
|
69 |
+
gl.glEnable(gl.GL_DEPTH_TEST)
|
70 |
+
gl.glEnable(gl.GL_CULL_FACE)
|
71 |
+
|
72 |
+
gl.glClearColor(0.0, 0.0, 0.0, 0.0)
|
73 |
+
self.clear()
|
74 |
+
self.world.draw()
|
75 |
+
|
76 |
+
gl.glFinish()
|
77 |
+
|
78 |
+
# input functions
|
79 |
+
|
80 |
+
def on_resize(self, width, height):
|
81 |
+
print(f"Resize {width} * {height}")
|
82 |
+
gl.glViewport(0, 0, width, height)
|
83 |
+
|
84 |
+
self.player.view_width = width
|
85 |
+
self.player.view_height = height
|
86 |
+
|
87 |
+
def on_mouse_press(self, x, y, button, modifiers):
|
88 |
+
if not self.mouse_captured:
|
89 |
+
self.mouse_captured = True
|
90 |
+
self.set_exclusive_mouse(True)
|
91 |
+
|
92 |
+
return
|
93 |
+
|
94 |
+
# handle breaking/placing blocks
|
95 |
+
|
96 |
+
def hit_callback(current_block, next_block):
|
97 |
+
if button == pyglet.window.mouse.RIGHT: self.world.try_set_block(current_block, self.holding, self.player.collider)
|
98 |
+
elif button == pyglet.window.mouse.LEFT: self.world.set_block(next_block, 0)
|
99 |
+
elif button == pyglet.window.mouse.MIDDLE: self.holding = self.world.get_block_number(next_block)
|
100 |
+
|
101 |
+
x, y, z = self.player.position
|
102 |
+
y += self.player.eyelevel
|
103 |
+
|
104 |
+
hit_ray = hit.Hit_ray(self.world, self.player.rotation, (x, y, z))
|
105 |
+
|
106 |
+
while hit_ray.distance < hit.HIT_RANGE:
|
107 |
+
if hit_ray.step(hit_callback):
|
108 |
+
break
|
109 |
+
|
110 |
+
def on_mouse_motion(self, x, y, delta_x, delta_y):
|
111 |
+
if self.mouse_captured:
|
112 |
+
sensitivity = 0.004
|
113 |
+
|
114 |
+
self.player.rotation[0] += delta_x * sensitivity
|
115 |
+
self.player.rotation[1] += delta_y * sensitivity
|
116 |
+
|
117 |
+
self.player.rotation[1] = max(-math.tau / 4, min(math.tau / 4, self.player.rotation[1]))
|
118 |
+
|
119 |
+
def on_mouse_drag(self, x, y, delta_x, delta_y, buttons, modifiers):
|
120 |
+
self.on_mouse_motion(x, y, delta_x, delta_y)
|
121 |
+
|
122 |
+
def on_key_press(self, key, modifiers):
|
123 |
+
if not self.mouse_captured:
|
124 |
+
return
|
125 |
+
|
126 |
+
if key == pyglet.window.key.D: self.player.input[0] += 1
|
127 |
+
elif key == pyglet.window.key.A: self.player.input[0] -= 1
|
128 |
+
elif key == pyglet.window.key.W: self.player.input[2] += 1
|
129 |
+
elif key == pyglet.window.key.S: self.player.input[2] -= 1
|
130 |
+
|
131 |
+
elif key == pyglet.window.key.SPACE : self.player.input[1] += 1
|
132 |
+
elif key == pyglet.window.key.LSHIFT: self.player.input[1] -= 1
|
133 |
+
elif key == pyglet.window.key.LCTRL : self.player.target_speed = player.SPRINTING_SPEED
|
134 |
+
|
135 |
+
elif key == pyglet.window.key.F:
|
136 |
+
self.player.flying = not self.player.flying
|
137 |
+
|
138 |
+
elif key == pyglet.window.key.G:
|
139 |
+
self.holding = random.randint(1, len(self.world.block_types) - 1)
|
140 |
+
|
141 |
+
elif key == pyglet.window.key.O:
|
142 |
+
self.world.save.save()
|
143 |
+
|
144 |
+
elif key == pyglet.window.key.R:
|
145 |
+
# how large is the world?
|
146 |
+
|
147 |
+
max_y = 0
|
148 |
+
|
149 |
+
max_x, max_z = (0, 0)
|
150 |
+
min_x, min_z = (0, 0)
|
151 |
+
|
152 |
+
for pos in self.world.chunks:
|
153 |
+
x, y, z = pos
|
154 |
+
|
155 |
+
max_y = max(max_y, (y + 1) * chunk.CHUNK_HEIGHT)
|
156 |
+
|
157 |
+
max_x = max(max_x, (x + 1) * chunk.CHUNK_WIDTH)
|
158 |
+
min_x = min(min_x, x * chunk.CHUNK_WIDTH)
|
159 |
+
|
160 |
+
max_z = max(max_z, (z + 1) * chunk.CHUNK_LENGTH)
|
161 |
+
min_z = min(min_z, z * chunk.CHUNK_LENGTH)
|
162 |
+
|
163 |
+
# get random X & Z coordinates to teleport the player to
|
164 |
+
|
165 |
+
x = random.randint(min_x, max_x)
|
166 |
+
z = random.randint(min_z, max_z)
|
167 |
+
|
168 |
+
# find height at which to teleport to, by finding the first non-air block from the top of the world
|
169 |
+
|
170 |
+
for y in range(chunk.CHUNK_HEIGHT - 1, -1, -1):
|
171 |
+
if not self.world.get_block_number((x, y, z)):
|
172 |
+
continue
|
173 |
+
|
174 |
+
self.player.teleport((x, y + 1, z))
|
175 |
+
break
|
176 |
+
|
177 |
+
elif key == pyglet.window.key.ESCAPE:
|
178 |
+
self.mouse_captured = False
|
179 |
+
self.set_exclusive_mouse(False)
|
180 |
+
|
181 |
+
def on_key_release(self, key, modifiers):
|
182 |
+
if not self.mouse_captured:
|
183 |
+
return
|
184 |
+
|
185 |
+
if key == pyglet.window.key.D: self.player.input[0] -= 1
|
186 |
+
elif key == pyglet.window.key.A: self.player.input[0] += 1
|
187 |
+
elif key == pyglet.window.key.W: self.player.input[2] -= 1
|
188 |
+
elif key == pyglet.window.key.S: self.player.input[2] += 1
|
189 |
+
|
190 |
+
elif key == pyglet.window.key.SPACE : self.player.input[1] -= 1
|
191 |
+
elif key == pyglet.window.key.LSHIFT: self.player.input[1] += 1
|
192 |
+
elif key == pyglet.window.key.LCTRL : self.player.target_speed = player.WALKING_SPEED
|
193 |
+
|
194 |
+
class Game:
|
195 |
+
def __init__(self):
|
196 |
+
self.config = gl.Config(double_buffer = True, major_version = 3, minor_version = 3, depth_size = 16)
|
197 |
+
self.window = Window(config = self.config, width = 800, height = 600, caption = "Minecraft clone", resizable = True, vsync = False)
|
198 |
+
|
199 |
+
def run(self):
|
200 |
+
pyglet.app.run()
|
201 |
+
|
202 |
+
if __name__ == "__main__":
|
203 |
+
game = Game()
|
204 |
+
game.run()
|
matrix.py
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import copy
|
3 |
+
import ctypes
|
4 |
+
import math
|
5 |
+
|
6 |
+
def copy_matrix(matrix):
|
7 |
+
return copy.deepcopy(matrix) # we need to use deepcopy since we're dealing with 2D arrays
|
8 |
+
|
9 |
+
clean_matrix = [[0.0 for x in range(4)] for x in range(4)]
|
10 |
+
identity_matrix = copy_matrix(clean_matrix)
|
11 |
+
|
12 |
+
identity_matrix[0][0] = 1.0
|
13 |
+
identity_matrix[1][1] = 1.0
|
14 |
+
identity_matrix[2][2] = 1.0
|
15 |
+
identity_matrix[3][3] = 1.0
|
16 |
+
|
17 |
+
def multiply_matrices(x_matrix, y_matrix):
|
18 |
+
result_matrix = copy_matrix(clean_matrix)
|
19 |
+
|
20 |
+
for i in range(4):
|
21 |
+
for j in range(4):
|
22 |
+
result_matrix[i][j] = \
|
23 |
+
(x_matrix[0][j] * y_matrix[i][0]) + \
|
24 |
+
(x_matrix[1][j] * y_matrix[i][1]) + \
|
25 |
+
(x_matrix[2][j] * y_matrix[i][2]) + \
|
26 |
+
(x_matrix[3][j] * y_matrix[i][3])
|
27 |
+
|
28 |
+
return result_matrix
|
29 |
+
|
30 |
+
class Matrix:
|
31 |
+
def __init__(self, base = None):
|
32 |
+
if type(base) == Matrix: self.data = copy_matrix(base.data)
|
33 |
+
elif type(base) == list: self.data = copy_matrix(base)
|
34 |
+
else: self.data = copy_matrix(clean_matrix)
|
35 |
+
|
36 |
+
def load_identity(self):
|
37 |
+
self.data = copy_matrix(identity_matrix)
|
38 |
+
|
39 |
+
def __mul__(self, matrix):
|
40 |
+
return Matrix(multiply_matrices(self.data, matrix.data))
|
41 |
+
|
42 |
+
def __imul__(self, matrix):
|
43 |
+
self.data = multiply_matrices(self.data, matrix.data)
|
44 |
+
|
45 |
+
def scale(self, x, y, z):
|
46 |
+
for i in range(4): self.data[0][i] *= x
|
47 |
+
for i in range(4): self.data[1][i] *= y
|
48 |
+
for i in range(4): self.data[2][i] *= z
|
49 |
+
|
50 |
+
def translate(self, x, y, z):
|
51 |
+
for i in range(4):
|
52 |
+
self.data[3][i] = self.data[3][i] + (self.data[0][i] * x + self.data[1][i] * y + self.data[2][i] * z)
|
53 |
+
|
54 |
+
def rotate(self, angle, x, y, z):
|
55 |
+
magnitude = math.sqrt(x * x + y * y + z * z)
|
56 |
+
|
57 |
+
x /= -magnitude
|
58 |
+
y /= -magnitude
|
59 |
+
z /= -magnitude
|
60 |
+
|
61 |
+
sin_angle = math.sin(angle)
|
62 |
+
cos_angle = math.cos(angle)
|
63 |
+
one_minus_cos = 1.0 - cos_angle
|
64 |
+
|
65 |
+
xx = x * x
|
66 |
+
yy = y * y
|
67 |
+
zz = z * z
|
68 |
+
|
69 |
+
xy = x * y
|
70 |
+
yz = y * z
|
71 |
+
zx = z * x
|
72 |
+
|
73 |
+
xs = x * sin_angle
|
74 |
+
ys = y * sin_angle
|
75 |
+
zs = z * sin_angle
|
76 |
+
|
77 |
+
rotation_matrix = copy_matrix(clean_matrix)
|
78 |
+
|
79 |
+
rotation_matrix[0][0] = (one_minus_cos * xx) + cos_angle
|
80 |
+
rotation_matrix[0][1] = (one_minus_cos * xy) - zs
|
81 |
+
rotation_matrix[0][2] = (one_minus_cos * zx) + ys
|
82 |
+
|
83 |
+
rotation_matrix[1][0] = (one_minus_cos * xy) + zs
|
84 |
+
rotation_matrix[1][1] = (one_minus_cos * yy) + cos_angle
|
85 |
+
rotation_matrix[1][2] = (one_minus_cos * yz) - xs
|
86 |
+
|
87 |
+
rotation_matrix[2][0] = (one_minus_cos * zx) - ys
|
88 |
+
rotation_matrix[2][1] = (one_minus_cos * yz) + xs
|
89 |
+
rotation_matrix[2][2] = (one_minus_cos * zz) + cos_angle
|
90 |
+
|
91 |
+
rotation_matrix[3][3] = 1.0
|
92 |
+
self.data = multiply_matrices(self.data, rotation_matrix)
|
93 |
+
|
94 |
+
def rotate_2d(self, x, y):
|
95 |
+
self.rotate(x, 0, 1.0, 0)
|
96 |
+
self.rotate(-y, math.cos(x), 0, math.sin(x))
|
97 |
+
|
98 |
+
def frustum(self, left, right, bottom, top, near, far):
|
99 |
+
deltax = right - left
|
100 |
+
deltay = top - bottom
|
101 |
+
deltaz = far - near
|
102 |
+
|
103 |
+
frustum_matrix = copy_matrix(clean_matrix)
|
104 |
+
|
105 |
+
frustum_matrix[0][0] = 2 * near / deltax
|
106 |
+
frustum_matrix[1][1] = 2 * near / deltay
|
107 |
+
|
108 |
+
frustum_matrix[2][0] = (right + left) / deltax
|
109 |
+
frustum_matrix[2][1] = (top + bottom) / deltay
|
110 |
+
frustum_matrix[2][2] = -(near + far) / deltaz
|
111 |
+
|
112 |
+
frustum_matrix[2][3] = -1.0
|
113 |
+
frustum_matrix[3][2] = -2 * near * far / deltaz
|
114 |
+
|
115 |
+
self.data = multiply_matrices(self.data, frustum_matrix)
|
116 |
+
|
117 |
+
def perspective(self, fovy, aspect, near, far):
|
118 |
+
frustum_y = math.tan(math.radians(fovy) / 2)
|
119 |
+
frustum_x = frustum_y * aspect
|
120 |
+
|
121 |
+
self.frustum(-frustum_x * near, frustum_x * near, -frustum_y * near, frustum_y * near, near, far)
|
122 |
+
|
123 |
+
def orthographic(self, left, right, bottom, top, near, far):
|
124 |
+
deltax = right - left
|
125 |
+
deltay = top - bottom
|
126 |
+
deltaz = far - near
|
127 |
+
|
128 |
+
orthographic_matrix = copy_matrix(identity_matrix)
|
129 |
+
|
130 |
+
orthographic_matrix[0][0] = 2.0 / deltax
|
131 |
+
orthographic_matrix[3][0] = -(right + left) / deltax
|
132 |
+
|
133 |
+
orthographic_matrix[1][1] = 2.0 / deltay
|
134 |
+
orthographic_matrix[3][1] = -(top + bottom) / deltay
|
135 |
+
|
136 |
+
orthographic_matrix[2][2] = 2.0 / deltax
|
137 |
+
orthographic_matrix[3][2] = -(near + far) / deltaz
|
138 |
+
|
139 |
+
self.data = multiply_matrices(self.data, orthographic_matrix)
|
player.py
ADDED
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
import entity
|
3 |
+
import matrix
|
4 |
+
|
5 |
+
WALKING_SPEED = 4.317
|
6 |
+
SPRINTING_SPEED = 7 # faster than in Minecraft, feels better
|
7 |
+
|
8 |
+
class Player(entity.Entity):
|
9 |
+
def __init__(self, world, shader, width, height):
|
10 |
+
super().__init__(world)
|
11 |
+
|
12 |
+
self.view_width = width
|
13 |
+
self.view_height = height
|
14 |
+
|
15 |
+
# create matrices
|
16 |
+
|
17 |
+
self.mv_matrix = matrix.Matrix()
|
18 |
+
self.p_matrix = matrix.Matrix()
|
19 |
+
|
20 |
+
# shaders
|
21 |
+
|
22 |
+
self.shader = shader
|
23 |
+
self.shader_matrix_location = self.shader.find_uniform(b"matrix")
|
24 |
+
|
25 |
+
# camera variables
|
26 |
+
|
27 |
+
self.eyelevel = self.height - 0.2
|
28 |
+
self.input = [0, 0, 0]
|
29 |
+
|
30 |
+
self.target_speed = WALKING_SPEED
|
31 |
+
self.speed = self.target_speed
|
32 |
+
|
33 |
+
def update(self, delta_time):
|
34 |
+
# process input
|
35 |
+
|
36 |
+
if delta_time * 20 > 1:
|
37 |
+
self.speed = self.target_speed
|
38 |
+
|
39 |
+
else:
|
40 |
+
self.speed += (self.target_speed - self.speed) * delta_time * 20
|
41 |
+
|
42 |
+
multiplier = self.speed * (1, 2)[self.flying]
|
43 |
+
|
44 |
+
if self.flying and self.input[1]:
|
45 |
+
self.accel[1] = self.input[1] * multiplier
|
46 |
+
|
47 |
+
if self.input[0] or self.input[2]:
|
48 |
+
angle = self.rotation[0] - math.atan2(self.input[2], self.input[0]) + math.tau / 4
|
49 |
+
|
50 |
+
self.accel[0] = math.cos(angle) * multiplier
|
51 |
+
self.accel[2] = math.sin(angle) * multiplier
|
52 |
+
|
53 |
+
if not self.flying and self.input[1] > 0:
|
54 |
+
self.jump()
|
55 |
+
|
56 |
+
# process physics & collisions &c
|
57 |
+
|
58 |
+
super().update(delta_time)
|
59 |
+
|
60 |
+
def update_matrices(self):
|
61 |
+
# create projection matrix
|
62 |
+
|
63 |
+
self.p_matrix.load_identity()
|
64 |
+
|
65 |
+
self.p_matrix.perspective(
|
66 |
+
90 + 10 * (self.speed - WALKING_SPEED) / (SPRINTING_SPEED - WALKING_SPEED),
|
67 |
+
float(self.view_width) / self.view_height, 0.1, 500)
|
68 |
+
|
69 |
+
# create modelview matrix
|
70 |
+
|
71 |
+
self.mv_matrix.load_identity()
|
72 |
+
self.mv_matrix.rotate_2d(self.rotation[0] + math.tau / 4, self.rotation[1])
|
73 |
+
self.mv_matrix.translate(-self.position[0], -self.position[1] - self.eyelevel, -self.position[2])
|
74 |
+
|
75 |
+
# modelviewprojection matrix
|
76 |
+
|
77 |
+
mvp_matrix = self.p_matrix * self.mv_matrix
|
78 |
+
self.shader.uniform_matrix(self.shader_matrix_location, mvp_matrix)
|
save.py
ADDED
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import nbtlib as nbt
|
2 |
+
import base36
|
3 |
+
|
4 |
+
import chunk
|
5 |
+
|
6 |
+
class Save:
|
7 |
+
def __init__(self, world, path = "save"):
|
8 |
+
self.world = world
|
9 |
+
self.path = path
|
10 |
+
|
11 |
+
def chunk_position_to_path(self, chunk_position):
|
12 |
+
x, y, z = chunk_position
|
13 |
+
|
14 |
+
chunk_path = '/'.join((self.path,
|
15 |
+
base36.dumps(x % 64), base36.dumps(z % 64),
|
16 |
+
f"c.{base36.dumps(x)}.{base36.dumps(z)}.dat"))
|
17 |
+
|
18 |
+
return chunk_path
|
19 |
+
|
20 |
+
def load_chunk(self, chunk_position):
|
21 |
+
# load the chunk file
|
22 |
+
|
23 |
+
chunk_path = self.chunk_position_to_path(chunk_position)
|
24 |
+
|
25 |
+
try:
|
26 |
+
chunk_blocks = nbt.load(chunk_path)["Level"]["Blocks"]
|
27 |
+
|
28 |
+
except FileNotFoundError:
|
29 |
+
return
|
30 |
+
|
31 |
+
# create chunk and fill it with the blocks from our chunk file
|
32 |
+
|
33 |
+
self.world.chunks[chunk_position] = chunk.Chunk(self.world, chunk_position)
|
34 |
+
|
35 |
+
for x in range(chunk.CHUNK_WIDTH):
|
36 |
+
for y in range(chunk.CHUNK_HEIGHT):
|
37 |
+
for z in range(chunk.CHUNK_LENGTH):
|
38 |
+
self.world.chunks[chunk_position].blocks[x][y][z] = chunk_blocks[
|
39 |
+
x * chunk.CHUNK_LENGTH * chunk.CHUNK_HEIGHT +
|
40 |
+
z * chunk.CHUNK_HEIGHT +
|
41 |
+
y]
|
42 |
+
|
43 |
+
def save_chunk(self, chunk_position):
|
44 |
+
x, y, z = chunk_position
|
45 |
+
|
46 |
+
# try to load the chunk file
|
47 |
+
# if it doesn't exist, create a new one
|
48 |
+
|
49 |
+
chunk_path = self.chunk_position_to_path(chunk_position)
|
50 |
+
|
51 |
+
try:
|
52 |
+
chunk_data = nbt.load(chunk_path)
|
53 |
+
|
54 |
+
except FileNotFoundError:
|
55 |
+
chunk_data = nbt.File({"": nbt.Compound({"Level": nbt.Compound()})})
|
56 |
+
|
57 |
+
chunk_data["Level"]["xPos"] = x
|
58 |
+
chunk_data["Level"]["zPos"] = z
|
59 |
+
|
60 |
+
# fill the chunk file with the blocks from our chunk
|
61 |
+
|
62 |
+
chunk_blocks = nbt.ByteArray([0] * (chunk.CHUNK_WIDTH * chunk.CHUNK_HEIGHT * chunk.CHUNK_LENGTH))
|
63 |
+
|
64 |
+
for x in range(chunk.CHUNK_WIDTH):
|
65 |
+
for y in range(chunk.CHUNK_HEIGHT):
|
66 |
+
for z in range(chunk.CHUNK_LENGTH):
|
67 |
+
chunk_blocks[
|
68 |
+
x * chunk.CHUNK_LENGTH * chunk.CHUNK_HEIGHT +
|
69 |
+
z * chunk.CHUNK_HEIGHT +
|
70 |
+
y] = self.world.chunks[chunk_position].blocks[x][y][z]
|
71 |
+
|
72 |
+
# save the chunk file
|
73 |
+
|
74 |
+
chunk_data["Level"]["Blocks"] = chunk_blocks
|
75 |
+
chunk_data.save(chunk_path, gzipped = True)
|
76 |
+
|
77 |
+
def load(self):
|
78 |
+
# for x in range(-16, 15):
|
79 |
+
# for y in range(-15, 16):
|
80 |
+
# self.load_chunk((x, 0, y))
|
81 |
+
|
82 |
+
# for x in range(-4, 4):
|
83 |
+
# for y in range(-4, 4):
|
84 |
+
# self.load_chunk((x, 0, y))
|
85 |
+
|
86 |
+
for x in range(-1, 1):
|
87 |
+
for y in range(-1, 1):
|
88 |
+
self.load_chunk((x, 0, y))
|
89 |
+
|
90 |
+
def save(self):
|
91 |
+
for chunk_position in self.world.chunks:
|
92 |
+
if chunk_position[1] != 0: # reject all chunks above and below the world limit
|
93 |
+
continue
|
94 |
+
|
95 |
+
chunk = self.world.chunks[chunk_position]
|
96 |
+
|
97 |
+
if chunk.modified:
|
98 |
+
self.save_chunk(chunk_position)
|
99 |
+
chunk.modified = False
|
shader.py
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ctypes
|
2 |
+
import pyglet.gl as gl
|
3 |
+
|
4 |
+
class Shader_error(Exception):
|
5 |
+
def __init__(self, message):
|
6 |
+
self.message = message
|
7 |
+
|
8 |
+
def create_shader(target, source_path):
|
9 |
+
# read shader source
|
10 |
+
|
11 |
+
source_file = open(source_path, "rb")
|
12 |
+
source = source_file.read()
|
13 |
+
source_file.close()
|
14 |
+
|
15 |
+
source_length = ctypes.c_int(len(source) + 1)
|
16 |
+
source_buffer = ctypes.create_string_buffer(source)
|
17 |
+
|
18 |
+
buffer_pointer = ctypes.cast(
|
19 |
+
ctypes.pointer(ctypes.pointer(source_buffer)),
|
20 |
+
ctypes.POINTER(ctypes.POINTER(ctypes.c_char)))
|
21 |
+
|
22 |
+
# compile shader
|
23 |
+
|
24 |
+
gl.glShaderSource(target, 1, buffer_pointer, ctypes.byref(source_length))
|
25 |
+
gl.glCompileShader(target)
|
26 |
+
|
27 |
+
# handle potential errors
|
28 |
+
|
29 |
+
log_length = gl.GLint(0)
|
30 |
+
gl.glGetShaderiv(target, gl.GL_INFO_LOG_LENGTH, ctypes.byref(log_length))
|
31 |
+
|
32 |
+
log_buffer = ctypes.create_string_buffer(log_length.value)
|
33 |
+
gl.glGetShaderInfoLog(target, log_length, None, log_buffer)
|
34 |
+
|
35 |
+
if log_length.value > 1:
|
36 |
+
raise Shader_error(str(log_buffer.value))
|
37 |
+
|
38 |
+
class Shader:
|
39 |
+
def __init__(self, vert_path, frag_path):
|
40 |
+
self.program = gl.glCreateProgram()
|
41 |
+
|
42 |
+
# create vertex shader
|
43 |
+
|
44 |
+
self.vert_shader = gl.glCreateShader(gl.GL_VERTEX_SHADER)
|
45 |
+
create_shader(self.vert_shader, vert_path)
|
46 |
+
gl.glAttachShader(self.program, self.vert_shader)
|
47 |
+
|
48 |
+
# create fragment shader
|
49 |
+
|
50 |
+
self.frag_shader = gl.glCreateShader(gl.GL_FRAGMENT_SHADER)
|
51 |
+
create_shader(self.frag_shader, frag_path)
|
52 |
+
gl.glAttachShader(self.program, self.frag_shader)
|
53 |
+
|
54 |
+
# link program and clean up
|
55 |
+
|
56 |
+
gl.glLinkProgram(self.program)
|
57 |
+
|
58 |
+
gl.glDeleteShader(self.vert_shader)
|
59 |
+
gl.glDeleteShader(self.frag_shader)
|
60 |
+
|
61 |
+
def __del__(self):
|
62 |
+
gl.glDeleteProgram(self.program)
|
63 |
+
|
64 |
+
def find_uniform(self, name):
|
65 |
+
return gl.glGetUniformLocation(self.program, ctypes.create_string_buffer(name))
|
66 |
+
|
67 |
+
def uniform_matrix(self, location, matrix):
|
68 |
+
gl.glUniformMatrix4fv(location, 1, gl.GL_FALSE, (gl.GLfloat * 16) (*sum(matrix.data, [])))
|
69 |
+
|
70 |
+
def use(self):
|
71 |
+
gl.glUseProgram(self.program)
|
subchunk.py
ADDED
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
SUBCHUNK_WIDTH = 4
|
2 |
+
SUBCHUNK_HEIGHT = 4
|
3 |
+
SUBCHUNK_LENGTH = 4
|
4 |
+
|
5 |
+
class Subchunk:
|
6 |
+
def __init__(self, parent, subchunk_position):
|
7 |
+
self.parent = parent
|
8 |
+
self.world = self.parent.world
|
9 |
+
|
10 |
+
self.subchunk_position = subchunk_position
|
11 |
+
|
12 |
+
self.local_position = (
|
13 |
+
self.subchunk_position[0] * SUBCHUNK_WIDTH,
|
14 |
+
self.subchunk_position[1] * SUBCHUNK_HEIGHT,
|
15 |
+
self.subchunk_position[2] * SUBCHUNK_LENGTH)
|
16 |
+
|
17 |
+
self.position = (
|
18 |
+
self.parent.position[0] + self.local_position[0],
|
19 |
+
self.parent.position[1] + self.local_position[1],
|
20 |
+
self.parent.position[2] + self.local_position[2])
|
21 |
+
|
22 |
+
# mesh variables
|
23 |
+
|
24 |
+
self.mesh_vertex_positions = []
|
25 |
+
self.mesh_tex_coords = []
|
26 |
+
self.mesh_shading_values = []
|
27 |
+
|
28 |
+
self.mesh_index_counter = 0
|
29 |
+
self.mesh_indices = []
|
30 |
+
|
31 |
+
def update_mesh(self):
|
32 |
+
self.mesh_vertex_positions = []
|
33 |
+
self.mesh_tex_coords = []
|
34 |
+
self.mesh_shading_values = []
|
35 |
+
|
36 |
+
self.mesh_index_counter = 0
|
37 |
+
self.mesh_indices = []
|
38 |
+
|
39 |
+
def add_face(face):
|
40 |
+
vertex_positions = block_type.vertex_positions[face].copy()
|
41 |
+
|
42 |
+
for i in range(4):
|
43 |
+
vertex_positions[i * 3 + 0] += x
|
44 |
+
vertex_positions[i * 3 + 1] += y
|
45 |
+
vertex_positions[i * 3 + 2] += z
|
46 |
+
|
47 |
+
self.mesh_vertex_positions.extend(vertex_positions)
|
48 |
+
|
49 |
+
indices = [0, 1, 2, 0, 2, 3]
|
50 |
+
for i in range(6):
|
51 |
+
indices[i] += self.mesh_index_counter
|
52 |
+
|
53 |
+
self.mesh_indices.extend(indices)
|
54 |
+
self.mesh_index_counter += 4
|
55 |
+
|
56 |
+
self.mesh_tex_coords.extend(block_type.tex_coords[face])
|
57 |
+
self.mesh_shading_values.extend(block_type.shading_values[face])
|
58 |
+
|
59 |
+
for local_x in range(SUBCHUNK_WIDTH):
|
60 |
+
for local_y in range(SUBCHUNK_HEIGHT):
|
61 |
+
for local_z in range(SUBCHUNK_LENGTH):
|
62 |
+
parent_lx = self.local_position[0] + local_x
|
63 |
+
parent_ly = self.local_position[1] + local_y
|
64 |
+
parent_lz = self.local_position[2] + local_z
|
65 |
+
|
66 |
+
block_number = self.parent.blocks[parent_lx][parent_ly][parent_lz]
|
67 |
+
|
68 |
+
if block_number:
|
69 |
+
block_type = self.world.block_types[block_number]
|
70 |
+
|
71 |
+
x, y, z = (
|
72 |
+
self.position[0] + local_x,
|
73 |
+
self.position[1] + local_y,
|
74 |
+
self.position[2] + local_z)
|
75 |
+
|
76 |
+
def can_render_face(position):
|
77 |
+
if not self.world.is_opaque_block(position):
|
78 |
+
if block_type.glass and self.world.get_block_number(position) == block_number:
|
79 |
+
return False
|
80 |
+
|
81 |
+
return True
|
82 |
+
|
83 |
+
return False
|
84 |
+
|
85 |
+
# if block is cube, we want it to check neighbouring blocks so that we don't uselessly render faces
|
86 |
+
# if block isn't a cube, we just want to render all faces, regardless of neighbouring blocks
|
87 |
+
# since the vast majority of blocks are probably anyway going to be cubes, this won't impact performance all that much; the amount of useless faces drawn is going to be minimal
|
88 |
+
|
89 |
+
if block_type.is_cube:
|
90 |
+
if can_render_face((x + 1, y, z)): add_face(0)
|
91 |
+
if can_render_face((x - 1, y, z)): add_face(1)
|
92 |
+
if can_render_face((x, y + 1, z)): add_face(2)
|
93 |
+
if can_render_face((x, y - 1, z)): add_face(3)
|
94 |
+
if can_render_face((x, y, z + 1)): add_face(4)
|
95 |
+
if can_render_face((x, y, z - 1)): add_face(5)
|
96 |
+
|
97 |
+
else:
|
98 |
+
for i in range(len(block_type.vertex_positions)):
|
99 |
+
add_face(i)
|
texture_manager.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ctypes
|
2 |
+
import pyglet
|
3 |
+
|
4 |
+
import pyglet.gl as gl
|
5 |
+
|
6 |
+
class Texture_manager:
|
7 |
+
def __init__(self, texture_width, texture_height, max_textures):
|
8 |
+
self.texture_width = texture_width
|
9 |
+
self.texture_height = texture_height
|
10 |
+
|
11 |
+
self.max_textures = max_textures
|
12 |
+
|
13 |
+
self.textures = []
|
14 |
+
|
15 |
+
self.texture_array = gl.GLuint(0)
|
16 |
+
gl.glGenTextures(1, self.texture_array)
|
17 |
+
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, self.texture_array)
|
18 |
+
|
19 |
+
gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MIN_FILTER, gl.GL_NEAREST)
|
20 |
+
gl.glTexParameteri(gl.GL_TEXTURE_2D_ARRAY, gl.GL_TEXTURE_MAG_FILTER, gl.GL_NEAREST)
|
21 |
+
|
22 |
+
gl.glTexImage3D(
|
23 |
+
gl.GL_TEXTURE_2D_ARRAY, 0, gl.GL_RGBA,
|
24 |
+
self.texture_width, self.texture_height, self.max_textures,
|
25 |
+
0, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, None)
|
26 |
+
|
27 |
+
def generate_mipmaps(self):
|
28 |
+
gl.glGenerateMipmap(gl.GL_TEXTURE_2D_ARRAY)
|
29 |
+
|
30 |
+
def add_texture(self, texture):
|
31 |
+
if not texture in self.textures:
|
32 |
+
self.textures.append(texture)
|
33 |
+
|
34 |
+
texture_image = pyglet.image.load(f"textures/{texture}.png").get_image_data()
|
35 |
+
gl.glBindTexture(gl.GL_TEXTURE_2D_ARRAY, self.texture_array)
|
36 |
+
|
37 |
+
gl.glTexSubImage3D(
|
38 |
+
gl.GL_TEXTURE_2D_ARRAY, 0,
|
39 |
+
0, 0, self.textures.index(texture),
|
40 |
+
self.texture_width, self.texture_height, 1,
|
41 |
+
gl.GL_RGBA, gl.GL_UNSIGNED_BYTE,
|
42 |
+
texture_image.get_data("RGBA", texture_image.width * 4))
|
vert.glsl
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#version 330
|
2 |
+
|
3 |
+
layout(location = 0) in vec3 vertex_position;
|
4 |
+
layout(location = 1) in vec3 tex_coords;
|
5 |
+
layout(location = 2) in float shading_value;
|
6 |
+
|
7 |
+
out vec3 local_position;
|
8 |
+
out vec3 interpolated_tex_coords;
|
9 |
+
out float interpolated_shading_value;
|
10 |
+
|
11 |
+
uniform mat4 matrix;
|
12 |
+
|
13 |
+
void main(void) {
|
14 |
+
local_position = vertex_position;
|
15 |
+
interpolated_tex_coords = tex_coords;
|
16 |
+
interpolated_shading_value = shading_value;
|
17 |
+
gl_Position = matrix * vec4(vertex_position, 1.0);
|
18 |
+
}
|
world.py
ADDED
@@ -0,0 +1,176 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import math
|
2 |
+
import random
|
3 |
+
|
4 |
+
import save
|
5 |
+
import chunk
|
6 |
+
|
7 |
+
import block_type
|
8 |
+
import texture_manager
|
9 |
+
|
10 |
+
# import custom block models
|
11 |
+
|
12 |
+
import models
|
13 |
+
|
14 |
+
class World:
|
15 |
+
def __init__(self):
|
16 |
+
self.texture_manager = texture_manager.Texture_manager(16, 16, 256)
|
17 |
+
self.block_types = [None]
|
18 |
+
|
19 |
+
# parse block type data file
|
20 |
+
|
21 |
+
blocks_data_file = open("data/blocks.mcpy")
|
22 |
+
blocks_data = blocks_data_file.readlines()
|
23 |
+
blocks_data_file.close()
|
24 |
+
|
25 |
+
for block in blocks_data:
|
26 |
+
if block[0] in ['\n', '#']: # skip if empty line or comment
|
27 |
+
continue
|
28 |
+
|
29 |
+
number, props = block.split(':', 1)
|
30 |
+
number = int(number)
|
31 |
+
|
32 |
+
# default block
|
33 |
+
|
34 |
+
name = "Unknown"
|
35 |
+
model = models.cube
|
36 |
+
texture = {"all": "unknown"}
|
37 |
+
|
38 |
+
# read properties
|
39 |
+
|
40 |
+
for prop in props.split(','):
|
41 |
+
prop = prop.strip()
|
42 |
+
prop = list(filter(None, prop.split(' ', 1)))
|
43 |
+
|
44 |
+
if prop[0] == "sameas":
|
45 |
+
sameas_number = int(prop[1])
|
46 |
+
|
47 |
+
name = self.block_types[sameas_number].name
|
48 |
+
texture = self.block_types[sameas_number].block_face_textures
|
49 |
+
model = self.block_types[sameas_number].model
|
50 |
+
|
51 |
+
elif prop[0] == "name":
|
52 |
+
name = eval(prop[1])
|
53 |
+
|
54 |
+
elif prop[0][:7] == "texture":
|
55 |
+
_, side = prop[0].split('.')
|
56 |
+
texture[side] = prop[1].strip()
|
57 |
+
|
58 |
+
elif prop[0] == "model":
|
59 |
+
model = eval(prop[1])
|
60 |
+
|
61 |
+
# add block type
|
62 |
+
|
63 |
+
_block_type = block_type.Block_type(self.texture_manager, name, texture, model)
|
64 |
+
|
65 |
+
if number < len(self.block_types):
|
66 |
+
self.block_types[number] = _block_type
|
67 |
+
|
68 |
+
else:
|
69 |
+
self.block_types.append(_block_type)
|
70 |
+
|
71 |
+
self.texture_manager.generate_mipmaps()
|
72 |
+
|
73 |
+
# load the world
|
74 |
+
|
75 |
+
self.save = save.Save(self)
|
76 |
+
|
77 |
+
self.chunks = {}
|
78 |
+
self.save.load()
|
79 |
+
|
80 |
+
for chunk_position in self.chunks:
|
81 |
+
self.chunks[chunk_position].update_subchunk_meshes()
|
82 |
+
self.chunks[chunk_position].update_mesh()
|
83 |
+
|
84 |
+
def get_chunk_position(self, position):
|
85 |
+
x, y, z = position
|
86 |
+
|
87 |
+
return (
|
88 |
+
math.floor(x / chunk.CHUNK_WIDTH),
|
89 |
+
math.floor(y / chunk.CHUNK_HEIGHT),
|
90 |
+
math.floor(z / chunk.CHUNK_LENGTH))
|
91 |
+
|
92 |
+
def get_local_position(self, position):
|
93 |
+
x, y, z = position
|
94 |
+
|
95 |
+
return (
|
96 |
+
int(x % chunk.CHUNK_WIDTH),
|
97 |
+
int(y % chunk.CHUNK_HEIGHT),
|
98 |
+
int(z % chunk.CHUNK_LENGTH))
|
99 |
+
|
100 |
+
def get_block_number(self, position):
|
101 |
+
x, y, z = position
|
102 |
+
chunk_position = self.get_chunk_position(position)
|
103 |
+
|
104 |
+
if not chunk_position in self.chunks:
|
105 |
+
return 0
|
106 |
+
|
107 |
+
lx, ly, lz = self.get_local_position(position)
|
108 |
+
|
109 |
+
block_number = self.chunks[chunk_position].blocks[lx][ly][lz]
|
110 |
+
return block_number
|
111 |
+
|
112 |
+
def is_opaque_block(self, position):
|
113 |
+
# get block type and check if it's opaque or not
|
114 |
+
# air counts as a transparent block, so test for that too
|
115 |
+
|
116 |
+
block_type = self.block_types[self.get_block_number(position)]
|
117 |
+
|
118 |
+
if not block_type:
|
119 |
+
return False
|
120 |
+
|
121 |
+
return not block_type.transparent
|
122 |
+
|
123 |
+
def set_block(self, position, number): # set number to 0 (air) to remove block
|
124 |
+
x, y, z = position
|
125 |
+
chunk_position = self.get_chunk_position(position)
|
126 |
+
|
127 |
+
if not chunk_position in self.chunks: # if no chunks exist at this position, create a new one
|
128 |
+
if number == 0:
|
129 |
+
return # no point in creating a whole new chunk if we're not gonna be adding anything
|
130 |
+
|
131 |
+
self.chunks[chunk_position] = chunk.Chunk(self, chunk_position)
|
132 |
+
|
133 |
+
if self.get_block_number(position) == number: # no point updating mesh if the block is the same
|
134 |
+
return
|
135 |
+
|
136 |
+
lx, ly, lz = self.get_local_position(position)
|
137 |
+
|
138 |
+
self.chunks[chunk_position].blocks[lx][ly][lz] = number
|
139 |
+
self.chunks[chunk_position].modified = True
|
140 |
+
|
141 |
+
self.chunks[chunk_position].update_at_position((x, y, z))
|
142 |
+
self.chunks[chunk_position].update_mesh()
|
143 |
+
|
144 |
+
cx, cy, cz = chunk_position
|
145 |
+
|
146 |
+
def try_update_chunk_at_position(chunk_position, position):
|
147 |
+
if chunk_position in self.chunks:
|
148 |
+
self.chunks[chunk_position].update_at_position(position)
|
149 |
+
self.chunks[chunk_position].update_mesh()
|
150 |
+
|
151 |
+
if lx == chunk.CHUNK_WIDTH - 1: try_update_chunk_at_position((cx + 1, cy, cz), (x + 1, y, z))
|
152 |
+
if lx == 0: try_update_chunk_at_position((cx - 1, cy, cz), (x - 1, y, z))
|
153 |
+
|
154 |
+
if ly == chunk.CHUNK_HEIGHT - 1: try_update_chunk_at_position((cx, cy + 1, cz), (x, y + 1, z))
|
155 |
+
if ly == 0: try_update_chunk_at_position((cx, cy - 1, cz), (x, y - 1, z))
|
156 |
+
|
157 |
+
if lz == chunk.CHUNK_LENGTH - 1: try_update_chunk_at_position((cx, cy, cz + 1), (x, y, z + 1))
|
158 |
+
if lz == 0: try_update_chunk_at_position((cx, cy, cz - 1), (x, y, z - 1))
|
159 |
+
|
160 |
+
def try_set_block(self, pos, num, collider):
|
161 |
+
# if we're trying to remove a block, whatever let it go through
|
162 |
+
|
163 |
+
if not num:
|
164 |
+
return self.set_block(pos, 0)
|
165 |
+
|
166 |
+
# make sure the block doesn't intersect with the passed collider
|
167 |
+
|
168 |
+
for block_collider in self.block_types[num].colliders:
|
169 |
+
if collider & (block_collider + pos):
|
170 |
+
return
|
171 |
+
|
172 |
+
self.set_block(pos, num)
|
173 |
+
|
174 |
+
def draw(self):
|
175 |
+
for chunk_position in self.chunks:
|
176 |
+
self.chunks[chunk_position].draw()
|