import bpy from math import atan2, pi import mathutils from mathutils import noise import heapq maze_rows = 20 maze_cols = 20 # Delete default floor if present # Our delete all function would have worked fine too if "Cube" in bpy.data.objects: bpy.data.objects["Cube"].select_set(True) bpy.ops.object.delete() floor_size = max((maze_rows, maze_cols)) + 2 bpy.ops.mesh.primitive_grid_add(location=(floor_size/2 - 1.5, floor_size/2 - 1.5, 0), size=floor_size, x_subdivisions=100, y_subdivisions=100, calc_uvs=True) floor = bpy.context.active_object # Floor uses stone texture material = bpy.data.materials.new(name=f"FaceMaterial") material.use_nodes = True texture = bpy.data.images.load("floor.jpg") floor.data.materials.append(material) bsdf_node = material.node_tree.nodes.get("Principled BSDF") texture_node = material.node_tree.nodes.new(type="ShaderNodeTexImage") texture_node.image = texture material.node_tree.links.new(texture_node.outputs["Color"], bsdf_node.inputs["Base Color"]) for i, poly in enumerate(floor.data.polygons): uv_layer = floor.data.uv_layers.active.data for j, loop_index in enumerate(poly.loop_indices): vco = floor.data.vertices[poly.vertices[j]].co x,y = (0.5 + vco.x, 0.5 + vco.y) uv_layer[loop_index].uv = (x, y) material = bpy.data.materials.new(name=f"WallMaterial") material.use_nodes = True texture = bpy.data.images.load("brick.jpg") bsdf_node = material.node_tree.nodes.get("Principled BSDF") texture_node = material.node_tree.nodes.new(type="ShaderNodeTexImage") texture_node.image = texture material.node_tree.links.new(texture_node.outputs["Color"], bsdf_node.inputs["Base Color"]) def top_panel(verts): zees = [v.z for v in verts] return zees.count(zees[0]) == 4 def x_aligned_panel(verts): exes = [v.x for v in verts] return exes.count(exes[0]) == 4 def make_wall_panel(location, x_aligned, rim=False): bpy.ops.mesh.primitive_cube_add(size=1, calc_uvs=True) wall = bpy.context.active_object wall.scale = (0.1, 1, 1) if x_aligned else (1, 0.1, .999) if rim: wall.scale.z *= 1.1 wall.location = location wall.data.materials.append(material) uv_layer = wall.data.uv_layers.active.data for poly in wall.data.polygons: verts = [wall.data.vertices[poly.vertices[i]].co for i in range(4)] if top_panel(verts) and x_aligned: for i, co in enumerate(((1, 1), (0.9, 1), (0.9, 0), (1, 0))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) elif top_panel(verts): for i, co in enumerate(((1, 1), (0, 1), (0, 0.9), (1, 0.9))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) elif x_aligned_panel(verts) and not x_aligned: for i, co in enumerate(((1, 1), (1, 0), (0.9, 0), (0.9, 1))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) elif not x_aligned_panel(verts) and x_aligned: for i, co in enumerate(((1, 1), (1, 0), (0.9, 0), (0.9, 1))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) else: for i, co in enumerate(((1, 1), (1, 0), (0, 0), (0, 1))): uv_layer[poly.loop_indices[i]].uv = mathutils.Vector(co) return wall unvisited = dict() for row in range(maze_rows): for col in range(maze_cols): unvisited[(row, col)] = 1 # Change this if you want a different maze # Modify noise in some way (add to the coordinates, different z, etc) def edge_weight(n1, n2): middle = ((n1[0] + n2[0]) / 2, (n1[1] + n2[1]) / 2) return noise.noise((middle[0], middle[1], 0)) start_point = (1, 1) # places_to_go format: node 1, node 2, weight places_to_go = [] waiting_edges = dict() def neighbors_costs(p): neighbors = [ (p[0], p[1] + 1), (p[0], p[1] - 1), (p[0] + 1, p[1]), (p[0] - 1, p[1])] for n in neighbors: nx, ny = n if nx < 0 or nx == maze_cols: continue if ny < 0 or ny == maze_rows: continue if not n in unvisited: continue # Caveat here: It assumes the no two edges have the same weight ew = edge_weight(p, n) waiting_edges[ew] = (p, n) places_to_go.append(edge_weight(p, n)) cancel_list = [] del unvisited[start_point] neighbors_costs(start_point) while len(unvisited) > 0: heapq.heapify(places_to_go) key = heapq.heappop(places_to_go) next_edge = waiting_edges[key] # We should probably delete it from waiting_edges if not next_edge[1] in unvisited: continue del unvisited[next_edge[1]] neighbors_costs(next_edge[1]) cancel_list.append(next_edge) # On Thursday, we were left with an incomplete maze # The problem is that in cancel_list, edges can be in either direction, but # we only checked one direction. Checking the other direction fixes it def check_cancel(edge): if edge in cancel_list: return True if (edge[1], edge[0]) in cancel_list: return True for row in range(0, maze_rows * 2 - 1): if row % 2: # x-aligned row for segment in range(maze_cols): # This links together (segment, row/2) and (segment, 1 + row/2) v1 = (segment, int(row/2)) v2 = (segment, 1 + int(row/2)) edge_id = (v1, v2) if check_cancel(edge_id): continue print("Adding: ", edge_id) make_wall_panel((row/2, segment, 0.5), True) else: for segment in range(maze_cols - 1): # This links together (segment, row/2) and (segment, 1 + row/2) v1 = (segment, int(row/2)) v2 = (1 + segment, int(row/2)) edge_id = (v1, v2) if check_cancel(edge_id): continue print("Adding: ", edge_id) make_wall_panel((row/2, segment + 0.5, 0.5), False) material = bpy.data.materials.new(name=f"EdgeMaterial") material.use_nodes = True texture = bpy.data.images.load("brick_2.png") bsdf_node = material.node_tree.nodes.get("Principled BSDF") texture_node = material.node_tree.nodes.new(type="ShaderNodeTexImage") texture_node.image = texture material.node_tree.links.new(texture_node.outputs["Color"], bsdf_node.inputs["Base Color"]) # Should return true for any panels inside the maze, false for rim panels def skip(row, segment): if row == -1 or row == maze_rows * 2 - 1: return False if segment == -1 or (segment == maze_cols - 1 and 0 == row % 2): return False return True for row in range(-1, maze_rows * 2): if row % 2: # x-aligned row for segment in range(0, maze_cols): if skip(row, segment): continue; make_wall_panel((row/2, segment, 0.5), True, True) else: for segment in range(-1, maze_cols): if skip(row, segment): continue; wall = make_wall_panel((row/2, segment + 0.5, 0.5), False, True) # This next section seals the corners if segment in [maze_cols-1, -1] and row == (maze_rows * 2)-2: wall.location.x += 0.0247 wall.scale.x += 0.05 elif segment in [maze_cols-1, -1] and row == 0: wall.location.x -= 0.0247 wall.scale.x += 0.05 def set_material_view_and_zoom(zoom_level=1.0): for area in bpy.context.screen.areas: if area.type == "VIEW_3D": for space in area.spaces: if space.type == "VIEW_3D": space.shading.type = "MATERIAL" space.region_3d.view_distance *= zoom_level set_material_view_and_zoom()