158 lines
6.7 KiB
Python
158 lines
6.7 KiB
Python
"""Deterministic voxel blueprint for the generated Gothic hall reference."""
|
|
from collections import Counter, defaultdict
|
|
import importlib
|
|
import re
|
|
|
|
ORIGIN = (-50, -60, -40)
|
|
|
|
class Voxels:
|
|
def __init__(self):
|
|
self.blocks = {}
|
|
self.stage = 'site'
|
|
self.owners = {}
|
|
|
|
def set(self, x, y, z, block):
|
|
assert all(isinstance(v, int) for v in (x, y, z)), (x, y, z)
|
|
assert -2 <= x <= 68 and -1 <= y <= 64 and -2 <= z <= 68, (x, y, z)
|
|
if not block.startswith('minecraft:'):
|
|
block = 'minecraft:' + block
|
|
assert re.fullmatch(r'minecraft:[a-z0-9_]+(?:\[[a-z0-9_=,]+\])?', block), block
|
|
if block == 'minecraft:air':
|
|
self.blocks.pop((x, y, z), None)
|
|
self.owners.pop((x, y, z), None)
|
|
else:
|
|
self.blocks[(x, y, z)] = block
|
|
self.owners[(x, y, z)] = self.stage
|
|
|
|
def box(self, x1, y1, z1, x2, y2, z2, block):
|
|
x1, x2 = sorted((x1, x2))
|
|
y1, y2 = sorted((y1, y2))
|
|
z1, z2 = sorted((z1, z2))
|
|
for y in range(y1, y2 + 1):
|
|
for z in range(z1, z2 + 1):
|
|
for x in range(x1, x2 + 1):
|
|
self.set(x, y, z, block)
|
|
|
|
def clear(self, x1, y1, z1, x2, y2, z2):
|
|
self.box(x1, y1, z1, x2, y2, z2, 'air')
|
|
|
|
def site(v):
|
|
v.box(1, 0, 1, 63, 0, 62, 'smooth_stone')
|
|
for z in (1, 62):
|
|
v.box(1, 0, z, 63, 0, z, 'polished_andesite')
|
|
for x in (1, 63):
|
|
v.box(x, 0, 1, x, 0, 62, 'polished_andesite')
|
|
# A few continuous paving bands give the terrace scale without noisy texture.
|
|
for x in (5, 33, 60):
|
|
v.box(x, 0, 2, x, 0, 61, 'stone_bricks')
|
|
for z in (4, 59):
|
|
v.box(2, 0, z, 62, 0, z, 'stone_bricks')
|
|
for step in range(1, 7):
|
|
z = step + 1
|
|
v.box(13, 1, z, 25, step, z, 'stone_bricks')
|
|
v.box(14, step, z, 24, step, z,
|
|
'stone_brick_stairs[facing=south,half=bottom,shape=straight,waterlogged=false]')
|
|
for x in (12, 26):
|
|
v.box(x, 1, z, x, step + 1, z, 'polished_andesite')
|
|
v.set(x, step + 2, z, 'stone_brick_slab[type=bottom,waterlogged=false]')
|
|
v.box(13, 1, 8, 25, 6, 9, 'stone_bricks')
|
|
for x in (8, 30, 35, 61):
|
|
for z in (6, 59):
|
|
v.box(x, 1, z, x + 1, 1, z + 1, 'polished_andesite')
|
|
v.box(x, 2, z, x + 1, 3, z + 1, 'stone_bricks')
|
|
v.box(x, 4, z, x + 1, 4, z + 1,
|
|
'stone_brick_slab[type=bottom,waterlogged=false]')
|
|
|
|
def connection(v):
|
|
# Two-level enclosed passage between the great hall and companion wing.
|
|
v.stage = 'connecting gallery'
|
|
v.box(31, 0, 34, 40, 6, 42, 'stone_bricks')
|
|
v.box(31, 7, 34, 40, 13, 34, 'stone_bricks')
|
|
v.box(31, 7, 42, 40, 13, 42, 'stone_bricks')
|
|
v.box(31, 13, 33, 40, 13, 43, 'polished_andesite')
|
|
v.box(31, 14, 34, 40, 14, 42, 'deepslate_tiles')
|
|
v.box(31, 15, 36, 40, 15, 40, 'deepslate_tiles')
|
|
v.box(31, 16, 38, 40, 16, 38, 'deepslate_tiles')
|
|
for z, facing, y in [(34, 'south', 14), (35, 'south', 14),
|
|
(36, 'south', 15), (37, 'south', 15),
|
|
(39, 'north', 15), (40, 'north', 15),
|
|
(41, 'north', 14), (42, 'north', 14)]:
|
|
v.box(31, y, z, 40, y, z,
|
|
f'deepslate_tile_stairs[facing={facing},half=bottom,shape=straight,waterlogged=false]')
|
|
v.clear(30, 7, 36, 32, 11, 39)
|
|
v.clear(33, 8, 36, 34, 11, 39)
|
|
v.clear(35, 9, 36, 40, 12, 39)
|
|
v.box(31, 6, 36, 32, 6, 39, 'spruce_planks')
|
|
for x, y in [(33, 6), (34, 7)]:
|
|
v.box(x, y, 36, x, y, 39,
|
|
'spruce_stairs[facing=east,half=bottom,shape=straight,waterlogged=false]')
|
|
v.box(35, 8, 36, 40, 8, 39, 'spruce_planks')
|
|
|
|
def build_scene():
|
|
v = Voxels()
|
|
site(v)
|
|
for name, stage in [('main_hall', 'great hall'), ('side_wing', 'side wing'), ('tower', 'bell tower')]:
|
|
v.stage = stage
|
|
importlib.import_module('builds.gothic_hall.' + name).build(v)
|
|
connection(v)
|
|
return v
|
|
|
|
def boxes(blocks):
|
|
"""Merge runs on X, then Z and Y, preserving the exact sparse block mask."""
|
|
rows = defaultdict(list)
|
|
for (x, y, z), block in blocks.items():
|
|
rows[(y, z, block)].append(x)
|
|
runs = []
|
|
for (y, z, block), xs in sorted(rows.items()):
|
|
xs.sort(); start = end = xs[0]
|
|
for x in xs[1:]:
|
|
if x == end + 1:
|
|
end = x
|
|
else:
|
|
runs.append((start, y, z, end, y, z, block)); start = end = x
|
|
runs.append((start, y, z, end, y, z, block))
|
|
merged = []
|
|
groups = defaultdict(list)
|
|
for x1, y1, z1, x2, y2, z2, block in runs:
|
|
groups[(x1, x2, y1, block)].append(z1)
|
|
for (x1, x2, y, block), zs in sorted(groups.items()):
|
|
zs.sort(); start = end = zs[0]
|
|
for z in zs[1:]:
|
|
if z == end + 1: end = z
|
|
else:
|
|
merged.append((x1, y, start, x2, y, end, block)); start = end = z
|
|
merged.append((x1, y, start, x2, y, end, block))
|
|
result = []; groups = defaultdict(list)
|
|
for x1, y1, z1, x2, y2, z2, block in merged:
|
|
groups[(x1, x2, z1, z2, block)].append(y1)
|
|
for (x1, x2, z1, z2, block), ys in sorted(groups.items()):
|
|
ys.sort(); start = end = ys[0]
|
|
for y in ys[1:]:
|
|
if y == end + 1: end = y
|
|
else:
|
|
result.append((x1, start, z1, x2, end, z2, block)); start = end = y
|
|
result.append((x1, start, z1, x2, end, z2, block))
|
|
return result
|
|
|
|
def manifest(v):
|
|
tiles = defaultdict(dict)
|
|
for p, block in v.blocks.items():
|
|
tiles[(p[1] // 8, p[2] // 16, p[0] // 16)][p] = block
|
|
batches = []
|
|
for key, data in sorted(tiles.items()):
|
|
recipe = []
|
|
for x1, y1, z1, x2, y2, z2, block in boxes(data):
|
|
lo = dict(zip(('x', 'y', 'z'), (x1 + ORIGIN[0], y1 + ORIGIN[1], z1 + ORIGIN[2])))
|
|
hi = dict(zip(('x', 'y', 'z'), (x2 + ORIGIN[0], y2 + ORIGIN[1], z2 + ORIGIN[2])))
|
|
recipe.append({'type': 'box', 'min': lo, 'max': hi, 'block': block})
|
|
positions = list(data)
|
|
minimum = [min(p[i] for p in positions) + ORIGIN[i] for i in range(3)]
|
|
maximum = [max(p[i] for p in positions) + ORIGIN[i] for i in range(3)]
|
|
batches.append({'key': '-'.join(map(str, key)), 'blocks': len(data),
|
|
'min': dict(zip(('x', 'y', 'z'), minimum)), 'max': dict(zip(('x', 'y', 'z'), maximum)),
|
|
'recipe': {'version': 1, 'operations': recipe},
|
|
'stages': dict(Counter(v.owners[p] for p in data))})
|
|
return {'name': 'Gothic hall reference reconstruction', 'origin': ORIGIN,
|
|
'blocks': len(v.blocks), 'stages': dict(Counter(v.owners.values())),
|
|
'palette': dict(Counter(v.blocks.values())), 'batches': batches}
|