static func generate_wall_mesh(corners, height):
	if corners.size() < 3:
		return null

	var st = SurfaceTool.new()
	var wall_up = Vector3.UP * height

	st.begin(Mesh.PRIMITIVE_TRIANGLE_STRIP)

	for corner in corners:
		var corner3D = Vector3(corner.x, 0, corner.y)

		st.add_vertex(corner3D + wall_up)
		st.add_vertex(corner3D)

	var first_corner = Vector3(corners[0].x, 0, corners[0].y)

	st.add_vertex(first_corner + wall_up)
	st.add_vertex(first_corner)

	st.index()
	st.generate_normals()
	st.generate_tangents()
	var mesh = st.commit()
	
	return mesh

## Generate a wall mesh with doors
## corners: Array of Vector2
## height: float
## doors: Array[Array[Vector3, Vector3]]
static func generate_wall_mesh_with_doors(corners, height, doors):
	if corners.size() < 3:
		return null

	var mesh = ArrayMesh.new()

	for i in range(0, corners.size()):
		var corner = corners[i]
		var next_corner = corners[(i + 1) % corners.size()]

		var forward = Vector3(next_corner.x - corner.x, 0, next_corner.y - corner.y).normalized()

		var points := PackedVector2Array()

		points.append(Vector2(0, 0))
		points.append(Vector2(0, height))
		points.append(Vector2(corner.distance_to(next_corner), height))
		points.append(Vector2(corner.distance_to(next_corner), 0))

		for door in doors:
			var door_point1 = Vector2(door[0].x, door[0].z)
			var door_point2 = Vector2(door[1].x, door[1].z)
			var proj_point1 = Geometry2D.get_closest_point_to_segment_uncapped(door_point1, corner, next_corner)
			var proj_point2 = Geometry2D.get_closest_point_to_segment_uncapped(door_point2, corner, next_corner)

			if proj_point1.distance_to(door_point1) > 0.02&&proj_point2.distance_to(door_point2) > 0.02:
				continue

			if proj_point1.distance_to(proj_point2) < 0.02:
				continue

			var point1_distance = corner.distance_to(proj_point1)
			var point2_distance = corner.distance_to(proj_point2)

			var door_points := PackedVector2Array()

			door_points.append(Vector2(point1_distance, -1))
			door_points.append(Vector2(point1_distance, door[0].y))
			door_points.append(Vector2(point2_distance, door[1].y))
			door_points.append(Vector2(point2_distance, -1))

			var clip = Geometry2D.clip_polygons(points, door_points)
			if clip.size() == 0:
				continue

			assert(clip.size() != 2, "Door clip should not create holes")

			points = clip[0]

		var edges = PackedInt32Array()
		for k in range(points.size()):
			edges.append(k)
			edges.append((k + 1) % points.size())

		var cdt: ConstrainedTriangulation = ConstrainedTriangulation.new()
		cdt.init(true, true, 0.1)

		cdt.insert_vertices(points)
		cdt.insert_edges(edges)

		cdt.erase_outer_triangles()

		points = cdt.get_all_vertices()
		var triangles: PackedInt32Array = cdt.get_all_triangles()

		var points_3d = PackedVector3Array()

		for k in range(points.size()):
			points_3d.append(Vector3(corner.x, 0, corner.y) + points[k].x * forward + Vector3(0, points[k].y, 0))

		mesh = _create_mesh_3d(points_3d, triangles, mesh)

	return mesh

static func generate_ceiling_mesh(corners):
	var points: PackedVector2Array = PackedVector2Array()
	var edges: PackedInt32Array = PackedInt32Array()
	var triangles: PackedInt32Array

	if corners.size() < 3:
		return null

	for i in range(corners.size()):
		var corner = corners[i]
		points.append(Vector2(corner.x, corner.y))
		edges.append(i)
		edges.append((i + 1) % corners.size())

	var cdt: ConstrainedTriangulation = ConstrainedTriangulation.new()

	cdt.init(true, true, 0.1)

	cdt.insert_vertices(points)
	cdt.insert_edges(edges)

	cdt.erase_outer_triangles()

	points = cdt.get_all_vertices()
	triangles = cdt.get_all_triangles()

	return _create_mesh_2d(points, triangles)

static func generate_wall_mesh_with_doors_grid(corners, height, doors, grid:=0.1):
	if corners.size() < 3:
		return null

	var mesh = ArrayMesh.new()

	for i in range(0, corners.size()):
		var corner = corners[i]
		var next_corner = corners[(i + 1) % corners.size()]

		var forward = Vector3(next_corner.x - corner.x, 0, next_corner.y - corner.y).normalized()

		var points := PackedVector2Array()

		points.append(Vector2(0, 0))
		points.append(Vector2(0, height))
		points.append(Vector2(corner.distance_to(next_corner), height))
		points.append(Vector2(corner.distance_to(next_corner), 0))

		for door in doors:
			var door_point1 = Vector2(door[0].x, door[0].z)
			var door_point2 = Vector2(door[1].x, door[1].z)
			var proj_point1 = Geometry2D.get_closest_point_to_segment_uncapped(door_point1, corner, next_corner)
			var proj_point2 = Geometry2D.get_closest_point_to_segment_uncapped(door_point2, corner, next_corner)

			if proj_point1.distance_to(door_point1) > 0.02&&proj_point2.distance_to(door_point2) > 0.02:
				continue

			if proj_point1.distance_to(proj_point2) < 0.02:
				continue

			var point1_distance = corner.distance_to(proj_point1)
			var point2_distance = corner.distance_to(proj_point2)

			var door_points := PackedVector2Array()

			door_points.append(Vector2(point1_distance, -1))
			door_points.append(Vector2(point1_distance, door[0].y))
			door_points.append(Vector2(point2_distance, door[1].y))
			door_points.append(Vector2(point2_distance, -1))

			var clip = Geometry2D.clip_polygons(points, door_points)
			if clip.size() == 0:
				continue

			assert(clip.size() != 2, "Door clip should not create holes")

			points = clip[0]

		# Subdivide edge to grid

		var new_points = PackedVector2Array()

		for k in range(points.size()):
			var point = points[k]
			var next_point = points[(k + 1) % points.size()]

			new_points.append(point)
			
			var steps = floor(point.distance_to(next_point) / grid)

			for x in range(1, steps):
				new_points.append(point + (next_point - point).normalized() * grid * x)

		points = new_points

		var edges = PackedInt32Array()
		for k in range(points.size()):
			edges.append(k)
			edges.append((k + 1) % points.size())

		# Subdivide inner polygon to grid
		var steps = ceil(Vector2(corner.distance_to(next_corner) / grid, height / grid))

		for y in range(1, steps.y):
			for x in range(1, steps.x):
				var point = Vector2(x * grid, y * grid)

				points.append(point)

		var cdt: ConstrainedTriangulation = ConstrainedTriangulation.new()
		cdt.init(true, true, 0.001)

		cdt.insert_vertices(points)
		cdt.insert_edges(edges)

		cdt.erase_outer_triangles()

		points = cdt.get_all_vertices()
		var triangles: PackedInt32Array = cdt.get_all_triangles()

		var points_3d = PackedVector3Array()

		for k in range(points.size()):
			points_3d.append(Vector3(corner.x, 0, corner.y) + points[k].x * forward + Vector3(0, points[k].y, 0))

		mesh = _create_mesh_3d(points_3d, triangles, mesh)

	return mesh

static func generate_wall_mesh_grid(corners, height, grid: Vector2=Vector2(0.1, 0.1)):
	if corners.size() < 3:
		return null

	var st = SurfaceTool.new()

	st.begin(Mesh.PRIMITIVE_TRIANGLES)

	for corner_i in range(corners.size()):
		var corner = Vector3(corners[corner_i].x, 0, corners[corner_i].y)
		var next_index = (corner_i + 1) % corners.size()
		var next_corner = Vector3(corners[next_index].x, 0, corners[next_index].y)

		var steps = ceil(Vector2((next_corner - corner).length() / grid.x, height / grid.y))

		var forward_dir = (next_corner - corner).normalized() * grid.x
		var up_dir = Vector3.UP * grid.y

		var close_distance = Vector2(1, 1)

		for y in range(0, steps.y):

			close_distance.x = 1

			if y == steps.y - 1:
					close_distance.y = fmod(height, grid.y) / grid.y

			for x in range(0, steps.x):
				var point = corner + forward_dir * x + Vector3.UP * grid.y * y

				if x == steps.x - 1:
					close_distance.x = fmod(corner.distance_to(next_corner), grid.x) / grid.x
				
				st.add_vertex(point)
				st.add_vertex(point + forward_dir * close_distance.x)
				st.add_vertex(point + up_dir * close_distance.y)

				st.add_vertex(point + forward_dir * close_distance.x)
				st.add_vertex(point + forward_dir * close_distance.x + up_dir * close_distance.y)
				st.add_vertex(point + up_dir * close_distance.y)

	st.index()
	st.generate_normals()
	st.generate_tangents()
	var mesh = st.commit()
	
	return mesh

static func generate_ceiling_mesh_grid(corners, grid: Vector2=Vector2(0.1, 0.1)):
	var points: PackedVector2Array = PackedVector2Array()
	var edges: PackedInt32Array = PackedInt32Array()
	var triangles: PackedInt32Array

	if corners.size() < 3:
		return null

	var min_val = Vector2(corners[0])
	var max_val = Vector2(corners[0])

	for i in range(corners.size()):
		var corner = corners[i]

		min_val.x = min(min_val.x, corner.x)
		min_val.y = min(min_val.y, corner.y)
		max_val.x = max(max_val.x, corner.x)
		max_val.y = max(max_val.y, corner.y)

		points.append(Vector2(corner.x, corner.y))
		edges.append(i)
		edges.append((i + 1) % corners.size())

	var size = max_val - min_val

	# Subdivide edges to grid
	for i in range(corners.size()):
		var corner = corners[i]
		var next_index = (i + 1) % corners.size()
		var next_corner = corners[next_index]

		var steps = ceil(Vector2((next_corner - corner).length() / grid.x, size.y / grid.y))

		var forward_dir = (next_corner - corner).normalized() * grid.x

		for x in range(1, steps.x):
			var point = corner + forward_dir * x

			points.append(Vector2(point.x, point.y))

	## Fill points insde the polygon
	for y in range(1, int(size.y / grid.y)):
		var x_intersections: Array[float] = []

		var y_start = Vector2(min_val.x, min_val.y + y * grid.y)
		var y_end = Vector2(max_val.x, min_val.y + y * grid.y)

		for i in range(corners.size()):
			var a = corners[i]
			var b = corners[(i + 1) % corners.size()]

			var result = Geometry2D.segment_intersects_segment(a, b, y_start, y_end)

			if result != null:
				x_intersections.append(result.x)

		var intersection_counter = 0

		x_intersections.sort()

		for x in range(1, int(size.x / grid.x)):
			var point = min_val + Vector2(x * grid.x, y * grid.y)

			for i in range(intersection_counter, x_intersections.size()):
				if x_intersections[i] < point.x:
					intersection_counter += 1

			var color = Color(1, 1, 0)

			if intersection_counter % 2 == 1:
				color = Color(1, 0, 1)
				points.append(point)

	var cdt: ConstrainedTriangulation = ConstrainedTriangulation.new()

	cdt.init(true, true, 0.1)

	cdt.insert_vertices(points)
	cdt.insert_edges(edges)

	cdt.erase_outer_triangles()

	points = cdt.get_all_vertices()
	triangles = cdt.get_all_triangles()

	return _create_mesh_2d(points, triangles)

static func _create_mesh_3d(points: PackedVector3Array, triangles: PackedInt32Array, existing=null):
	var st = SurfaceTool.new()

	st.begin(Mesh.PRIMITIVE_TRIANGLES)

	for i in range(points.size()):
		st.add_vertex(Vector3(points[i].x, points[i].y, points[i].z))

	for i in range(triangles.size()):
		st.add_index(triangles[i])

	st.index()
	st.generate_normals()
	st.generate_tangents()

	if existing != null:
		return st.commit(existing)

	return st.commit()

static func _create_mesh_2d(points: PackedVector2Array, triangles: PackedInt32Array):
	var st = SurfaceTool.new()

	st.begin(Mesh.PRIMITIVE_TRIANGLES)

	for i in range(points.size()):
		st.add_vertex(Vector3(points[i].x, 0, points[i].y))

	for i in range(triangles.size()):
		st.add_index(triangles[i])

	st.index()
	st.generate_normals()
	st.generate_tangents()

	var mesh = st.commit()

	return mesh