extends Node3D @export_category("Level Generator") @export var width: int = 40 @export var height: int = 40 @export var min_dim: int = 5 @export_category("Geometry Generator") ## Geometry generation config @export var wall_thickness: float = 1 var min_room_size: int = min_dim * min_dim enum Direction {LEFT, RIGHT} class BSPNode: var axis: int var min_dims: Vector2i var max_dims: Vector2i var left: BSPNode var right: BSPNode func _init(p_axis: int, p_min_dims: Vector2i, p_max_dims: Vector2i, p_left: BSPNode, p_right: BSPNode): axis = p_axis min_dims = p_min_dims max_dims = p_max_dims left = p_left right = p_right func generate_level(axis: int, min_space: Vector2i, max_space: Vector2i, depth: int = 0) -> BSPNode: var dims = max_space - min_space var new_axis = (axis + 1) % 2 # 10% we stop here and just create a big room if (depth > 2 and randi_range(0, 9) == 0) \ or dims.x * dims.y <= min_dim * min_dim \ or dims[new_axis] / 2 < min_dim: return BSPNode.new(new_axis, min_space, max_space, null, null) # Calculate min and max ranges so that a split # doesn't create a room that violates min dimensions var min_value = min_space[axis] + min_dim var max_value = max_space[axis] - min_dim var split = randi_range(min_value, max_value) #print("Spliting axis ", axis, " at ", split) var left_min_space = min_space var left_max_space = max_space left_max_space[axis] = split var left = generate_level(new_axis, left_min_space, left_max_space, depth + 1) var right_min_space = min_space right_min_space[axis] = split var right_max_space = max_space var right = generate_level(new_axis, right_min_space, right_max_space, depth + 1) return BSPNode.new(axis, min_space, max_space, left, right) func is_leaf(node: BSPNode) -> bool: return node.left == null and node.right == null func get_children_on_axis(node: BSPNode, axis: int, value: int) -> Array[BSPNode]: if is_leaf(node): if node.min_dims[axis] == value or node.max_dims[axis] == value: return [node] else: return [] else: return get_children_on_axis(node.left, axis, value) + get_children_on_axis(node.right, axis, value) func generate_doors(node: BSPNode): var left_children = get_children_on_axis(node.left, node.axis, node.left.max_dims[node.axis]) var right_children = get_children_on_axis(node.right, node.axis, node.right.min_dims[node.axis]) print(left_children) print(right_children) func generate_geo(map: BSPNode): # TODO figure out where to place walls if map.left == null and map.right == null: var box = CSGBox3D.new() var p1 = Vector3(map.min_dims.x + wall_thickness/2, 0, map.min_dims.y + wall_thickness/2) var p2 = Vector3(map.max_dims.x - wall_thickness/2, 0.1, map.max_dims.y - wall_thickness/2) box.size = p2 - p1 var pos_p1 = Vector3(map.min_dims.x, 0, map.min_dims.y) var pos_p2 = Vector3(map.max_dims.x, 0, map.max_dims.y) var offset = (pos_p2 - pos_p1) / 2 box.position = p1 + offset - Vector3(0, 1, 0) add_child(box) else: generate_geo(map.left) generate_geo(map.right) # Generate the wall between the two var wall = CSGBox3D.new() var axis = 0 if map.axis == 0 else 2 var other_axis = 2 if axis == 0 else 0 var max_dim = Vector3(map.max_dims.x, 0, map.max_dims.y) var min_dim = Vector3(map.min_dims.x, 0, map.min_dims.y) wall.size[map.axis] = wall_thickness wall.size[other_axis] = max_dim[other_axis] - min_dim[other_axis] wall.size[1] = 2 var pos2d = map.left.max_dims var pos3d = Vector3(pos2d.x, 0, pos2d.y) wall.position = pos3d - (wall.size/2) + Vector3(1, 1, 1) add_child(wall) func _ready() -> void: var starting_axis = randi_range(0, 1) var min_space = Vector2i(0, 0) var max_space = Vector2i(width, height) var map = generate_level(starting_axis, min_space, max_space) generate_doors(map) generate_geo(map)