Skip to content

Instantly share code, notes, and snippets.

@Teekeks
Last active May 30, 2025 07:17
Show Gist options
  • Save Teekeks/0d2820ac1a17ae723a5a0e0733ed17fb to your computer and use it in GitHub Desktop.
Save Teekeks/0d2820ac1a17ae723a5a0e0733ed17fb to your computer and use it in GitHub Desktop.
# Generates a minecraft litematic file which defines a circle of beacons that surround the center with a dome spanning over all.
#
# The beacons are placed in such a way that the beams are visually as uniformly spaced as possible from the center.
# The beams are colored in a smooth color transition from one beacon to the next.
# Each beam is surrounded by a random scattering of "particles" (glass panes) of appropriate colors.
#
# Inspired by this video: https://www.youtube.com/watch?v=VcsEm7FnheU by Squibble
import math
from itertools import accumulate
from litemapy import Region, BlockState
import random
######################################################################################################################################################
# SETTINGS
######################################################################################################################################################
MIN_DISTANCE = 53 # minimum distance the beacon could be placed from the center
MAX_DISTANCE = 91 # maximum distance the beacon could be placed from the center
STEP_RESOLUTION = 5000 # the nr of places the program should check per 1 block distance from the center
COLOR_OFFSET = 1 # nr of blocks the color stack is offset from the beacon
BLOCK_BEACON_BASE = BlockState("minecraft:iron_block") # the block to use for the beacon base
BEACON_BASE_SIZE = 4 # the height of the beacon base, between 1 and 4
BEAM_PARTICLE_HEIGHT_OFFSET = 10 # the height offset of the particles measured from the top of the color stack
BEAM_PARTICLE_HEIGHT = 180 # the height of the particle beam surrounding the beacon
BEAM_PARTICLE_MIN_DISTANCE = 1 # the minimum distance beam particles should have from the beacon beam
BEAM_PARTICLE_MAX_DISTANCE = 3 # the maximum distance beam particles should have from the beacon beam
BEAM_PARTICLE_WEIGHT = 2 # the weight set on the middle of the particles
BEAM_PARTICLE_COUNT = 100 # the number of particles arround the beacon beam
DOME_OFFSET = 22 # Y offset of the dome
DOME_SIZE = 180
DOME_CUTTOF = 96
DOME_LIGHT_BLOCK = BlockState("minecraft:end_rod", facing="down")
DOME_LIGHT_COUNT = 1500
DOME_PARTICLE_COUNT = 10000
COLOR_TAPE = [ # the colors to use for the rainbow colors
[BlockState("minecraft:blue_stained_glass"), BlockState("minecraft:blue_stained_glass_pane")],
[BlockState("minecraft:light_blue_stained_glass"), BlockState("minecraft:light_blue_stained_glass_pane")],
[BlockState("minecraft:green_stained_glass"), BlockState("minecraft:green_stained_glass_pane")],
[BlockState("minecraft:yellow_stained_glass"), BlockState("minecraft:yellow_stained_glass_pane")],
[BlockState("minecraft:orange_stained_glass"), BlockState("minecraft:orange_stained_glass_pane")],
[BlockState("minecraft:red_stained_glass"), BlockState("minecraft:red_stained_glass_pane")],
]
TAPE_SEQUENCE = [ # the sequence to use for color mixing, 0 is the previous color, 1 is the current one
[0, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 1, 1, 0, 0],
[0, 0, 0, 1, 0],
[0, 1, 0, 1, 0],
[0, 0, 1, 1, 0],
[0, 1, 1, 1, 0],
[0, 0, 0, 0, 1],
[0, 1, 0, 0, 1],
[0, 0, 1, 0, 1],
[0, 1, 1, 0, 1],
[0, 0, 0, 1, 1],
[0, 1, 0, 1, 1],
[0, 0, 1, 1, 1],
[0, 1, 1, 1, 1],
]
######################################################################################################################################################
######################################################################################################################################################
SCHEM_OFFSET = MAX_DISTANCE + 2
BLOCK_BEACON = BlockState("minecraft:beacon")
base_height = 1 + BEACON_BASE_SIZE + COLOR_OFFSET + len(TAPE_SEQUENCE[0]) + BEAM_PARTICLE_HEIGHT_OFFSET + BEAM_PARTICLE_HEIGHT
if base_height < DOME_SIZE + DOME_OFFSET + 1:
base_height = DOME_SIZE + DOME_OFFSET + 1
BLOCK_CLEAR = BlockState("minecraft:air")
if SCHEM_OFFSET < DOME_SIZE:
SCHEM_OFFSET = DOME_SIZE
reg = Region(-SCHEM_OFFSET,
0,
-SCHEM_OFFSET,
SCHEM_OFFSET * 2 + 1,
base_height,
SCHEM_OFFSET * 2 + 1)
def gen_color_sequence():
res = []
for idx, _c in enumerate(COLOR_TAPE):
_color, _beam_color = _c
prev, prev_beam = COLOR_TAPE[idx - 1]
for seq in TAPE_SEQUENCE:
res.append([[_color if _a == 1 else prev for _a in seq], [_beam_color if _a == 1 else prev_beam for _a in seq]])
return res
class Vec2d:
def __init__(self, x, y):
self.x = x
self.y = y
@classmethod
def from_angle(cls, angle):
return cls(math.sin(angle), math.cos(angle))
def length(self) -> float:
return abs(math.sqrt(self.x ** 2 + self.y ** 2))
def normalize(self) -> "Vec2d":
length = self.length()
self.x, self.y = self.x / length, self.y / length
return self
def __mul__(self, other):
return Vec2d(self.x * other, self.y * other)
def int_dist(self):
return math.dist([self.x, self.y], [round(self.x), round(self.y)])
def __repr__(self):
return f"Vec2d({self.x}, {self.y})"
def rand_sphere_coords(sphere_size, center):
theta = random.random() * 2 * math.pi
phi = random.random() * math.pi
return [
round(sphere_size * math.cos(theta) * math.sin(phi) + center[0]),
round(sphere_size * math.sin(theta) * math.sin(phi) + center[1]),
round(sphere_size * math.cos(phi) + center[2]),
]
beacon_spots = []
color_stacks = gen_color_sequence()
print(f'generating beacon circle with {len(color_stacks)} beacons')
step = 360 / len(color_stacks)
for i, cs in enumerate(color_stacks):
color_stack, beams = cs
vec = Vec2d.from_angle(math.radians(step * i)).normalize()
vecs = sorted([vec * (dist / STEP_RESOLUTION)
for dist in range(MIN_DISTANCE * STEP_RESOLUTION, MAX_DISTANCE * STEP_RESOLUTION)],
key=lambda d: d.int_dist())[0]
best = [round(vecs.x + SCHEM_OFFSET - 0.5), round(vecs.y + SCHEM_OFFSET - 0.5)]
print(f'found position for beacon {i + 1} at {best}')
beacon_spots.append((best[0], best[1]))
reg[best[0], BEACON_BASE_SIZE, best[1]] = BLOCK_BEACON
# beacon base
for base_size in range(BEACON_BASE_SIZE):
for x in range(-1 - base_size, 2 + base_size):
for y in range(-1 - base_size, 2 + base_size):
reg[best[0] + x, BEACON_BASE_SIZE - base_size - 1, best[1] + y] = BLOCK_BEACON_BASE
# color stack
for _i, c in enumerate(color_stack, start=1+COLOR_OFFSET+BEACON_BASE_SIZE):
reg[best[0], _i, best[1]] = c
# beam particles
particle_height_start = 1+COLOR_OFFSET+BEACON_BASE_SIZE+BEAM_PARTICLE_HEIGHT_OFFSET
part_mid = BEAM_PARTICLE_HEIGHT // 2
cum_weight = list(accumulate([(part_mid - abs(part_mid - _x)) * BEAM_PARTICLE_WEIGHT for _x in range(BEAM_PARTICLE_HEIGHT)]))
height_vals = list(range(BEAM_PARTICLE_HEIGHT))
for _ in range(BEAM_PARTICLE_COUNT):
rand_vec = (Vec2d.from_angle(math.radians(random.uniform(0, 360))).normalize() *
random.uniform(BEAM_PARTICLE_MIN_DISTANCE, BEAM_PARTICLE_MAX_DISTANCE))
reg[round(best[0] + rand_vec.x),
particle_height_start + random.choices(height_vals, cum_weights=cum_weight, k=1)[0],
round(best[1] + rand_vec.y)] = random.choice(beams)
dome_mid = [SCHEM_OFFSET, DOME_OFFSET, SCHEM_OFFSET]
light_count = 0
while light_count < DOME_LIGHT_COUNT:
rand_spot = rand_sphere_coords(DOME_SIZE, dome_mid)
if rand_spot[1] < DOME_CUTTOF:
continue
light_count += 1
if light_count % 1000 == 0:
print(f'generated {light_count} lights in dome...')
reg[rand_spot[0], rand_spot[1], rand_spot[2]] = DOME_LIGHT_BLOCK
print(f'done generating {light_count} lights in dome')
dome_count = 0
beams = [_x[1] for _x in COLOR_TAPE]
while dome_count < DOME_PARTICLE_COUNT:
rand_spot = rand_sphere_coords(DOME_SIZE, dome_mid)
if rand_spot[1] < DOME_CUTTOF:
continue
dist_vec = Vec2d(rand_spot[0] - SCHEM_OFFSET, rand_spot[2] - SCHEM_OFFSET)
if dist_vec.length() > random.random() * DOME_SIZE:
continue
# do not overlap with a beacon beam
dome_count += 1
reg[rand_spot[0], rand_spot[1], rand_spot[2]] = random.choice(beams)
if dome_count % 1000 == 0:
print(f'generated {dome_count} dome particles...')
print(f'done generating {dome_count} dome particles...')
print('cleaning path above beacon beams...')
clean_start_height = 1+COLOR_OFFSET+BEACON_BASE_SIZE + len(TAPE_SEQUENCE[0])
for beam in beacon_spots:
for y in range(clean_start_height, reg.height):
reg[beam[0], y, beam[1]] = BLOCK_CLEAR
print(f'writing file...')
schem = reg.as_schematic(name="beacon_circle")
schem.save("beacon_circle.litematic")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment