from Lights.Lights import Light from Objects.Objects import Object from Objects.Renderable import Renderable from Objects.Structure import Structure from MatrixStuff.Transformations import translate from OpenGL.GLU import * from OpenGL.GL import * import math import numpy as np import random import sys # Plate Types SEA_PLATE = 0 CONTINENTAL_PLATE = 1 # Rock types EMPTY = 0 SEA_PLATE_STONE = 1 MAGMATIC_STONE = 2 METAMORPH_STONE = 3 SEDIMENTAL_STONE = 4 SEDIMENT = 5 class WorldChunk(Structure): def __init__(self, width: int, length: int, height: int, programs: dict): assert width > 0, 'Width must be greater than 0' assert length > 0, 'length must be greater than 0' assert height > 0, 'height must be greater than 0' super(WorldChunk, self).__init__() self.visible = [] self.content = [] self.entities = [] self.lights = [] self.width = width self.length = length self.height = height self.programs = programs for x in range(width): self.content.append([]) self.visible.append([]) for y in range(length): self.content[x].append([]) self.visible[x].append([]) for z in range(height): self.content[x][y].append(None) self.visible[x][y].append(4) def put_object(self, x: int, y: int, z: int, new_object: Object): assert 0 <= x < self.width, 'Put out of bounds for x coordinate! Must be between 0 and %i' % self.width assert 0 <= y < self.length, 'Put out of bounds for y coordinate! Must be between 0 and %i' % self.length assert 0 <= z < self.height, 'Put out of bounds for z coordinate! Must be between 0 and %i' % self.height no_visibility_changes = (self.content[x][y][z] is None) == (new_object is None) self.content[x][y][z] = new_object new_object.translate(translate(x, y, z)) change = -1 if new_object is not None else 1 visible_carry_over = [] if not no_visibility_changes: if x + 1 >= self.width: visible_carry_over.append((1, 0, 0, change)) else: self.visible[x + 1][y][z] += change if x - 1 < 0: visible_carry_over.append((-1, 0, 0, change)) else: self.visible[x - 1][y][z] += change if y + 1 >= self.length: visible_carry_over.append((0, 1, 0, change)) else: self.visible[x][y + 1][z] += change if y - 1 < 0: visible_carry_over.append((0, -1, 0, change)) else: self.visible[x][y - 1][z] += change if z + 1 >= self.height: visible_carry_over.append((0, 0, 1, change)) else: self.visible[x][y][z + 1] += change if z - 1 < 0: visible_carry_over.append((0, 0, -1, change)) else: self.visible[x][y][z - 1] += change return visible_carry_over def get_object(self, x: int, y: int, z: int): assert 0 <= x < self.width, 'Put out of bounds for x coordinate! Must be between 0 and %i' % self.width assert 0 <= y < self.length, 'Put out of bounds for y coordinate! Must be between 0 and %i' % self.length assert 0 <= z < self.height, 'Put out of bounds for z coordinate! Must be between 0 and %i' % self.height return self.content[x][y][z] def apply_visible_carry_over(self, x: int, y: int, z: int, change: int): assert 0 <= x < self.width, 'Apply visible out of bounds for x coordinate! Must be between 0 and %i' % self.width assert 0 <= y < self.length, 'Apply visible out of bounds for y coordinate! Must be between 0 and %i' % self.length assert 0 <= z < self.height, 'Apply visible out of bounds for z coordinate! Must be between 0 and %i' % self.height self.visible[x][y][z] += change def set_visibility(self, x: int, y: int, z: int, visibility: int): assert 0 <= x < self.width, 'Apply visible out of bounds for x coordinate! Must be between 0 and %i' % self.width assert 0 <= y < self.length, 'Apply visible out of bounds for y coordinate! Must be between 0 and %i' % self.length assert 0 <= z < self.height, 'Apply visible out of bounds for z coordinate! Must be between 0 and %i' % self.height self.visible[x][y][z] = visibility def buildvertexArrays(self): if self.dirty: self.clearVertexArrays() glEnableClientState(GL_VERTEX_ARRAY) glEnableClientState(GL_TEXTURE_COORD_ARRAY) glEnableClientState(GL_NORMAL_ARRAY) glEnableClientState(GL_COLOR_ARRAY) self.vais = {} objects = {} counts = {} for x in range(self.width): for y in range(self.length): for z in range(self.height): if self.content[x][y][z] is not None: # and self.visible[x][y][z] > 0: TODO: check visibility... if self.programs[type(self.content[x][y][z])] not in objects.keys(): objects[self.programs[type(self.content[x][y][z])]] = [] counts[self.programs[type(self.content[x][y][z])]] = 0 objects[self.programs[type(self.content[x][y][z])]].append(self.content[x][y][z]) counts[self.programs[type(self.content[x][y][z])]] += 1 for key, object_list in objects.items(): tvai = GLuint(0) tpbi = GLuint(0) tcbi = GLuint(0) tsbi = GLuint(0) glGenVertexArrays(1, tvai) glBindVertexArray(tvai) vid = glGetAttribLocation(key, "in_position") glEnableVertexAttribArray(vid) tpbi = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, tpbi) positions = [] for o in object_list: positions.append(o.pos[0] + self.x_offset) positions.append(o.pos[1] + self.y_offset) positions.append(o.pos[2] + self.z_offset) glBufferData(GL_ARRAY_BUFFER, np.array(positions, dtype=np.float32), GL_STATIC_DRAW) glVertexAttribPointer(vid, 3, GL_FLOAT, GL_FALSE, 0, None) self.check_error("Could not create position buffer") colors = [] for o in object_list: colors.append(o.color[0]) colors.append(o.color[1]) colors.append(o.color[2]) tcbi = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, tcbi) glBufferData(GL_ARRAY_BUFFER, np.array(colors, dtype=np.float32), GL_STATIC_DRAW) vc = glGetAttribLocation(key, "MyInColor") if vc != -1: glEnableVertexAttribArray(vc) glVertexAttribPointer(vc, 3, GL_FLOAT, GL_FALSE, 0, None) self.check_error("Could not create color buffer") if hasattr(object_list[0], 'size'): sizes = [] for o in object_list: sizes.append(o.size[0]) sizes.append(o.size[1]) sizes.append(o.size[2]) tsbi = glGenBuffers(1) glBindBuffer(GL_ARRAY_BUFFER, tsbi) glBufferData(GL_ARRAY_BUFFER, np.array(sizes, dtype=np.float32), GL_STATIC_DRAW) vs = glGetAttribLocation(key, "MyInSize") if vs != -1: glEnableVertexAttribArray(vs) glVertexAttribPointer(vs, 3, GL_FLOAT, GL_FALSE, 0, None) self.check_error("Could not create size buffer") glBindVertexArray(0) self.vais[key] = (tvai, tpbi, tcbi, tsbi, counts[key]) self.dirty = False def render(self, proj_matrix, geometry_rot_matrix, alternate_programs=None, preselected_program=None, projection_pos=None, rot_pos=None): super(WorldChunk, self).render(proj_matrix, geometry_rot_matrix, alternate_programs, preselected_program, projection_pos, rot_pos) for entity in self.entities: entity.render(proj_matrix, geometry_rot_matrix, alternate_programs, preselected_program, projection_pos, rot_pos) def set_color(self, x: int, y: int, z: int, r: float, g: float, b: float): assert 0 <= x < self.width, 'Put out of bounds for x coordinate! Must be between 0 and %i' % self.width assert 0 <= y < self.length, 'Put out of bounds for y coordinate! Must be between 0 and %i' % self.length assert 0 <= z < self.height, 'Put out of bounds for z coordinate! Must be between 0 and %i' % self.height if self.content[x][y][z] is not None: self.content[x][y][z].setColor(r, g, b) self.dirty = True class World(Renderable): def __init__(self, chunk_size_x: int, chunk_size_y: int, chunk_size_z: int, chunk_n_x: int, chunk_n_y: int, chunk_n_z: int, programs: dict): super(World, self).__init__() self.chunk_size_x = chunk_size_x self.chunk_size_y = chunk_size_y self.chunk_size_z = chunk_size_z self.chunk_n_x = chunk_n_x self.chunk_n_y = chunk_n_y self.chunk_n_z = chunk_n_z self.programs = programs self.fault_nodes = [] self.fault_lines = [] self.plates = None self.directions = None self.num_plates = 0 self.stone = None self.faults = None self.chunks: [[[WorldChunk]]] = [] for x in range(chunk_n_x): self.chunks.append([]) for y in range(chunk_n_y): self.chunks[x].append([]) for z in range(chunk_n_z): self.chunks[x][y].append(None) def generate(self, seed: int=None, sea_plate_height: int = 50, continental_plate_height: int = 200): if seed is None: seed = random.randrange(2**32) seed = 229805811 print('Generation seed is %i' % seed) random.seed(seed) np.random.seed(seed) node_n = self.chunk_n_x + self.chunk_n_y total_x = self.chunk_n_x * self.chunk_size_x total_y = self.chunk_n_y * self.chunk_size_y nodes = [] for _ in range(node_n): nodes.append([random.randint(0, total_x - 1), random.randint(0, total_y - 1)]) # connections = np.random.randint(2, 5, len(nodes)) #np.zeros(len(nodes)) + 3 connections = (np.abs(np.random.normal(0, 5, len(nodes))) + 2).astype(np.int) def calc_min_vector(start, end): dx = end[0] - start[0] wrapped_dx = dx % total_x dy = end[1] - start[1] wrapped_dy = dy % total_y vector = np.array([dx, dy]) if wrapped_dx < abs(dx): vector[0] = wrapped_dx if wrapped_dy < abs(dy): vector[1] = wrapped_dy return vector def is_intersecting_any(start, end, edges): vec1 = calc_min_vector(start, end) for (start2_index, end2_index) in edges: start2 = nodes[start2_index] end2 = nodes[end2_index] vec2 = calc_min_vector(start2, end2) norm1 = vec1 / np.sqrt(np.sum(np.square(vec1))) norm2 = vec2 / np.sqrt(np.sum(np.square(vec2))) # parrallel parallel_threshold = 0.0001 if np.sqrt(np.sum(np.square(norm1 - norm2))) < parallel_threshold or np.sqrt(np.sum(np.square(norm1 + norm2))) < parallel_threshold: t = (start[0] - start2[0]) / vec2[0] t2 = (end[0] - start2[0]) / vec2[0] s = (start2[0] - start[0]) / vec1[0] s2 = (end2[0] - start[0]) / vec1[0] if (start2[1] + t * vec2[1]) - start[1] < parallel_threshold: if (0 <= t <= 1.0 and 0 <= t2 <= 1.0) or (0 <= s <= 1.0 and 0 <= s2 <= 1.0): return True else: if start != start2 and end != end2 and end != start2 and start != end2: t = (vec1[0] * start[1] + vec1[1] * start2[0] - vec1[1] * start[0] - start2[1] * vec1[0]) / (vec2[1] * vec1[0] - vec2[0] * vec1[1]) if 0 <= t <= 1.0: intersection = np.array(start2) + vec2 * t s = (intersection[0] - start[0]) / vec1[0] if 0 <= s <= 1.0: return True return False for index, node in enumerate(nodes): distances = [] for other_index, other_node in enumerate(nodes): if node != other_node and (index, other_index) not in self.fault_lines and\ (other_index, index) not in self.fault_lines: if (not is_intersecting_any(node, other_node, self.fault_lines)) and (not is_intersecting_any(other_node, node, self.fault_lines)): distances.append((other_index, np.sqrt(np.sum(np.square(calc_min_vector(node, other_node)))))) distances.sort(key=lambda element: element[1]) while connections[index] > 0 and len(distances) > 0: self.fault_lines.append((index, distances[0][0])) connections[distances[0][0]] -= 1 connections[index] -= 1 distances.pop(0) self.fault_nodes = nodes plates = np.zeros((total_x, total_y)) faults = np.zeros((total_x, total_y)) - 1 plate_bordering_fault = {} # draw fault lines for fault_index, fault_line in enumerate(self.fault_lines): start = self.fault_nodes[fault_line[0]] end = self.fault_nodes[fault_line[1]] vector = calc_min_vector(start, end) vector = vector / np.sqrt(np.sum(np.square(vector))) point = np.array(start, dtype=np.float) plate_bordering_fault[fault_index] = [] while np.sqrt(np.sum(np.square(point - np.array(end)))) > 0.5: plates[int(point[0]), int(point[1])] = -1 if faults[int(point[0]), int(point[1])] == -1: faults[int(point[0]), int(point[1])] = fault_index elif faults[int(point[0]), int(point[1])] != fault_index: faults[int(point[0]), int(point[1])] = -2 point += 0.5 * vector point[0] %= total_x point[1] %= total_y self.faults = faults plate = 1 while np.any(plates == 0): start = np.where(plates == 0) start = (start[0][0], start[1][0]) plates[start] = plate work_list = [start] while len(work_list) > 0: work = work_list.pop() up = (work[0], (work[1] + 1) % total_y) down = (work[0], (work[1] - 1) % total_y) left = ((work[0] - 1) % total_x, work[1]) right = ((work[0] + 1) % total_x, work[1]) if plates[up] == -1 and plates[down] == -1 and plates[left] == -1 and plates[right] == -1: plates[work] = -1 continue if plates[up] <= 0: if plates[up] == 0: work_list.append(up) plates[up] = plate if plates[down] <= 0: if plates[down] == 0: work_list.append(down) plates[down] = plate if plates[left] <= 0: if plates[left] == 0: work_list.append(left) plates[left] = plate if plates[right] <= 0: if plates[right] == 0: work_list.append(right) plates[right] = plate if faults[up] > -1: if plate not in plate_bordering_fault[faults[up]]: plate_bordering_fault[faults[up]].append(plate) if faults[down] > -1: if plate not in plate_bordering_fault[faults[down]]: plate_bordering_fault[faults[down]].append(plate) if faults[left] > -1: if plate not in plate_bordering_fault[faults[left]]: plate_bordering_fault[faults[left]].append(plate) if faults[right] > -1: if plate not in plate_bordering_fault[faults[right]]: plate_bordering_fault[faults[right]].append(plate) plate += 1 plate_num = plate for plate in range(1, plate_num): if np.sum(plates == plate) < 20: plates[plates == plate] = -1 for key, item in plate_bordering_fault.items(): if plate in item: item.remove(plate) directions = np.zeros((total_x, total_y, 3)) coords = np.zeros((total_x, total_y, 2)) for x in range(total_x): for y in range(total_y): coords[x, y, 0] = x coords[x, y, 1] = y for fault_index, fault_line in enumerate(self.fault_lines): start = self.fault_nodes[fault_line[0]] end = self.fault_nodes[fault_line[1]] vector = calc_min_vector(start, end) vector = vector / np.sqrt(np.sum(np.square(vector))) perpendicular = np.array([vector[1], -vector[0]]) if len(plate_bordering_fault[fault_index]) == 2: for plate in plate_bordering_fault[fault_index]: vecs = coords - np.array(start) lengths = np.sqrt(np.sum(np.square(vecs), axis=2, keepdims=True)) norm_vecs = vecs / lengths scalars = np.sum(norm_vecs * perpendicular, axis=2, keepdims=True) scalars[lengths == 0] = 0 end_vecs = coords - np.array(end) end_lengths = np.sqrt(np.sum(np.square(end_vecs), axis=2, keepdims=True)) end_min_length = np.min(end_lengths[np.logical_and(plates == plate, end_lengths[:, :, 0] > 0)]) end_min_length_scalar = scalars[np.logical_and(plates == plate, end_lengths[:, :, 0] == end_min_length)][0, 0] min_length = np.min(lengths[np.logical_and(plates == plate, lengths[:, :, 0] > 0)]) min_length_scalar = scalars[np.logical_and(plates == plate, lengths[:, :, 0] == min_length)][0, 0] mean_scalar = np.mean(scalars[plates == plate]) if (min_length_scalar / abs(min_length_scalar)) == (end_min_length_scalar / abs(end_min_length_scalar)): scalar = min_length_scalar else: if (min_length_scalar / abs(min_length_scalar)) == (mean_scalar / abs(mean_scalar)): scalar = min_length_scalar else: scalar = end_min_length_scalar directions[plates == plate, :2] += perpendicular * (scalar / abs(scalar)) pass for x in range(total_x): for y in range(total_y): if plates[x, y] == -1: plate = np.max(plates[x - 1: x + 1, y - 1: y + 1]) plates[x, y] = plate self.plates = plates self.directions = directions self.num_plates = plate_num # max height will be three times the continental height # sea level will be at one and a half time continental height # with the continental plates top end ending there # sea plates will be flush at the bottom end max_height = 3 * continental_plate_height sea_level = int(1.5 * continental_plate_height) lower_level = sea_level - continental_plate_height upper_sea_plate_level = lower_level + sea_plate_height # stone kinds: 0: lava/air, 1: sea_plate, 2: magmatic_continental, 3: metamorph, 4: sedimental_rock, 5: sediment self.stone = np.zeros((total_x, total_y, max_height), np.int) plate_to_type = {} for plate in range(1, plate_num): if random.randint(1, 2) == 1: self.stone[plates == plate, lower_level:upper_sea_plate_level] = SEA_PLATE_STONE plate_to_type[plate] = SEA_PLATE else: self.stone[plates == plate, lower_level:sea_level] = MAGMATIC_STONE plate_to_type[plate] = CONTINENTAL_PLATE pass def set_color(self, x: int, y: int, z: int, r: float, g: float, b: float): x = x % (self.chunk_size_x * self.chunk_n_x) y = y % (self.chunk_size_y * self.chunk_n_y) z = z % (self.chunk_size_z * self.chunk_n_z) chunk_x = int(x / self.chunk_size_x) chunk_y = int(y / self.chunk_size_y) chunk_z = int(z / self.chunk_size_z) if self.chunks[chunk_x][chunk_y][chunk_z] is not None: self.chunks[chunk_x][chunk_y][chunk_z].set_color(x % self.chunk_size_x, y % self.chunk_size_y, z % self.chunk_size_z, r, g, b) def put_object(self, x: int, y: int, z: int, new_object: Object): x = x % (self.chunk_size_x * self.chunk_n_x) y = y % (self.chunk_size_y * self.chunk_n_y) z = z % (self.chunk_size_z * self.chunk_n_z) chunk_x = int(x / self.chunk_size_x) chunk_y = int(y / self.chunk_size_y) chunk_z = int(z / self.chunk_size_z) if self.chunks[chunk_x][chunk_y][chunk_z] is None: self.chunks[chunk_x][chunk_y][chunk_z] = WorldChunk(self.chunk_size_x, self.chunk_size_y, self.chunk_size_z, self.programs) self.chunks[chunk_x][chunk_y][chunk_z].x_offset = chunk_x * self.chunk_size_x self.chunks[chunk_x][chunk_y][chunk_z].y_offset = chunk_y * self.chunk_size_z self.chunks[chunk_x][chunk_y][chunk_z].z_offset = chunk_z * self.chunk_size_y carry_overs = self.chunks[chunk_x][chunk_y][chunk_z].put_object(x % self.chunk_size_x, y % self.chunk_size_y, z % self.chunk_size_z, new_object) for carry_over in carry_overs: if self.chunks[(chunk_x + carry_over[0]) % self.chunk_n_x][(chunk_y + carry_over[1]) % self.chunk_n_y][(chunk_z + carry_over[2]) % self.chunk_n_z] is not None: self.chunks[ (chunk_x + carry_over[0]) % self.chunk_n_x][ (chunk_y + carry_over[1]) % self.chunk_n_y][ (chunk_z + carry_over[2]) % self.chunk_n_z].apply_visible_carry_over( (x + carry_over[0]) % self.chunk_size_x, (y + carry_over[1]) % self.chunk_size_y, (z + carry_over[2]) % self.chunk_size_z, carry_over[3]) self.chunks[ (chunk_x + carry_over[0]) % self.chunk_n_x][ (chunk_y + carry_over[1]) % self.chunk_n_y][ (chunk_z + carry_over[2]) % self.chunk_n_z].dirty = True visibility = 6 neighbour = self.get_object(x - 1, y, z) if neighbour is not None: visibility -= 1 neighbour = self.get_object(x + 1, y, z) if neighbour is not None: visibility -= 1 neighbour = self.get_object(x, y - 1, z) if neighbour is not None: visibility -= 1 neighbour = self.get_object(x, y + 1, z) if neighbour is not None: visibility -= 1 neighbour = self.get_object(x, y, z - 1) if neighbour is not None: visibility -= 1 neighbour = self.get_object(x, y, z + 1) if neighbour is not None: visibility -= 1 self.chunks[chunk_x][chunk_y][chunk_z].set_visibility(x % self.chunk_size_x, y % self.chunk_size_y, z % self.chunk_size_z, visibility) self.chunks[chunk_x][chunk_y][chunk_z].dirty = True def get_object(self, x: int, y: int, z: int): x = x % (self.chunk_size_x * self.chunk_n_x) y = y % (self.chunk_size_y * self.chunk_n_y) z = z % (self.chunk_size_z * self.chunk_n_z) chunk_x = int(x / self.chunk_size_x) chunk_y = int(y / self.chunk_size_y) chunk_z = int(z / self.chunk_size_z) if self.chunks[chunk_x][chunk_y][chunk_z] is None: return None return self.chunks[chunk_x][chunk_y][chunk_z].get_object(x % self.chunk_size_x, y % self.chunk_size_y, z % self.chunk_size_z) def render(self, proj_matrix, geometry_rot_matrix, alternate_programs=None, preselected_program=None, projection_pos=None, rot_pos=None): if preselected_program is not None: for x in range(self.chunk_n_x): for y in range(self.chunk_n_y): for z in range(self.chunk_n_z): if self.chunks[x][y][z] is not None: self.chunks[x][y][z].render(proj_matrix, geometry_rot_matrix, alternate_programs, preselected_program, projection_pos, rot_pos) else: for _, program_id in self.programs.items(): if alternate_programs == None: used_program_id = program_id else: assert program_id in alternate_programs.keys() used_program_id = alternate_programs[program_id] glUseProgram(used_program_id) self.check_error("Renderingprogram is not initialized!") projection = glGetUniformLocation(used_program_id, 'projModelViewMatrix') rot = glGetUniformLocation(used_program_id, 'rotMatrix') glUniformMatrix3fv(rot, 1, GL_FALSE, np.array(geometry_rot_matrix)) glUniformMatrix4fv(projection, 1, GL_FALSE, np.array(proj_matrix)) for x in range(self.chunk_n_x): for y in range(self.chunk_n_y): for z in range(self.chunk_n_z): if self.chunks[x][y][z] is not None: self.chunks[x][y][z].render(proj_matrix, geometry_rot_matrix, alternate_programs, used_program_id, projection, rot) def add_light(self, x: float, y: float, z: float, l: Light)-> Light: x = x % (self.chunk_size_x * self.chunk_n_x) y = y % (self.chunk_size_y * self.chunk_n_y) z = z % (self.chunk_size_z * self.chunk_n_z) chunk_x = int(x / self.chunk_size_x) chunk_y = int(y / self.chunk_size_y) chunk_z = int(z / self.chunk_size_z) if self.chunks[chunk_x][chunk_y][chunk_z] is None: self.chunks[chunk_x][chunk_y][chunk_z] = WorldChunk(self.chunk_size_x, self.chunk_size_y, self.chunk_size_z, self.programs) self.chunks[chunk_x][chunk_y][chunk_z].x_offset = chunk_x * self.chunk_size_x self.chunks[chunk_x][chunk_y][chunk_z].y_offset = chunk_y * self.chunk_size_z self.chunks[chunk_x][chunk_y][chunk_z].z_offset = chunk_z * self.chunk_size_y self.chunks[chunk_x][chunk_y][chunk_z].lights.append(l) l.pos = [x, y, z] return l def remove_light(self, l: Light): chunk_x = int(l.pos[0] / self.chunk_size_x) chunk_y = int(l.pos[1] / self.chunk_size_y) chunk_z = int(l.pos[2] / self.chunk_size_z) if self.chunks[chunk_x][chunk_y][chunk_z] is None: return False if l in self.chunks[chunk_x][chunk_y][chunk_z].lights: self.chunks[chunk_x][chunk_y][chunk_z].lights.remove(l) return True else: return False def move_light(self, l: Light, target_x: float, target_y: float, target_z: float): self.remove_light(l) self.add_light(target_x, target_y, target_z, l) def get_lights_to_render(self, pos, distance): distance_x = math.ceil(float(distance) / self.chunk_size_x) distance_y = math.ceil(float(distance) / self.chunk_size_y) distance_z = math.ceil(float(distance) / self.chunk_size_z) pos_x = int(pos[0] / self.chunk_size_x) pos_y = int(pos[1] / self.chunk_size_y) pos_z = int(pos[2] / self.chunk_size_z) lights = [] for x in range(distance_x): for y in range(distance_y): for z in range(distance_z): chunk = self.chunks[(pos_x + x) % self.chunk_n_x][(pos_y + y) % self.chunk_n_y][(pos_z + z) % self.chunk_n_z] if chunk is not None: lights += chunk.lights return lights