This repository has been archived on 2024-06-13. You can view files and clone it, but cannot push or open issues or pull requests.
mcedit/filters/decliff.py
2012-10-25 00:09:56 -10:00

279 lines
10 KiB
Python

"""
DeCliff filter contributed by Minecraft Forums user "DrRomz"
Originally posted here:
http://www.minecraftforum.net/topic/13807-mcedit-minecraft-world-editor-compatible-with-mc-beta-18/page__st__3940__p__7648793#entry7648793
"""
from numpy import zeros, array
import itertools
from pymclevel import alphaMaterials
am = alphaMaterials
# Consider below materials when determining terrain height
blocks = [
am.Stone,
am.Grass,
am.Dirt,
am.Bedrock,
am.Sand,
am.Sandstone,
am.Clay,
am.Gravel,
am.GoldOre,
am.IronOre,
am.CoalOre,
am.LapisLazuliOre,
am.DiamondOre,
am.RedstoneOre,
am.RedstoneOreGlowing,
am.Netherrack,
am.SoulSand,
am.Glowstone
]
terrainBlocktypes = [b.ID for b in blocks]
terrainBlockmask = zeros((256,), dtype='bool')
# Truth table used to calculate terrain height
# trees, leaves, etc. sit on top of terrain
terrainBlockmask[terrainBlocktypes] = True
inputs = (
# Option to limit change to raise_cliff_floor / lower_cliff_top
# Default is to adjust both and meet somewhere in the middle
("Raise/Lower", ("Both", "Lower Only", "Raise Only")),
)
#
# Calculate the maximum adjustment that can be made from
# cliff_pos in direction dir (-1/1) keeping terain at most
# maxstep blocks away from previous column
def maxadj(heightmap, slice_no, cliff_pos, dir, pushup, maxstep, slice_width):
ret = 0
if dir < 0:
if cliff_pos < 2:
return 0
end = 0
else:
if cliff_pos > slice_width - 2:
return 0
end = slice_width - 1
for cur_pos in range(cliff_pos, end, dir):
if pushup:
ret = ret + \
max([0, maxstep - dir * heightmap[slice_no, cur_pos] + \
dir * heightmap[slice_no, cur_pos + dir]])
else:
ret = ret + \
min([0, -maxstep + dir * heightmap[slice_no, cur_pos] - \
dir * heightmap[slice_no, cur_pos + dir]])
return ret
#
# Raise/lower column at cliff face by adj and decrement change as we move away
# from the face. Each level will be at most maxstep blocks from those beside it.
#
# This function dosn't actually change anything, but just sets array 'new'
# with the desired height.
def adjheight(orig, new, slice_no, cliff_pos, dir, adj, can_adj, maxstep, slice_width):
cur_adj = adj
prev = 0
done_adj = 0
if dir < 0:
end = 1
else:
end = slice_width - 1
if adj == 0 or can_adj == 0:
for cur_pos in range(cliff_pos, end, dir):
new[slice_no, cur_pos] = orig[slice_no, cur_pos]
else:
for cur_pos in range(cliff_pos, end, dir):
if adj > 0:
done_adj = done_adj + \
max([0, maxstep - orig[slice_no, cur_pos] + \
orig[slice_no, cur_pos + dir]])
if orig[slice_no, cur_pos] - \
orig[slice_no, cur_pos + dir] > 0:
cur_adj = max([0, cur_adj - orig[slice_no, cur_pos] + \
orig[slice_no, cur_pos + dir]])
prev = adj - cur_adj
else:
done_adj = done_adj + \
min([0, -maxstep + \
orig[slice_no, cur_pos] - \
orig[slice_no, cur_pos + dir]])
if orig[slice_no, cur_pos] - \
orig[slice_no, cur_pos + dir] > 0:
cur_adj = min([0, cur_adj + orig[slice_no, cur_pos] - orig[slice_no, cur_pos + dir]])
prev = adj - cur_adj
new[slice_no, cur_pos] = max([0, orig[slice_no, cur_pos] + cur_adj])
if cur_adj != 0 and \
abs(prev) < abs(int(adj * done_adj / can_adj)):
cur_adj = cur_adj + (prev - int(adj * done_adj / can_adj))
prev = int(adj * done_adj / can_adj)
new[slice_no, end] = orig[slice_no, end]
def perform(level, box, options):
if box.volume > 16000000:
raise ValueError("Volume too big for this filter method!")
RLOption = options["Raise/Lower"]
schema = level.extractSchematic(box)
schema.removeEntitiesInBox(schema.bounds)
schema.removeTileEntitiesInBox(schema.bounds)
terrainBlocks = terrainBlockmask[schema.Blocks]
coords = terrainBlocks.nonzero()
# Swap values around so long edge of selected rectangle is first
# - the long edge is assumed to run parallel to the cliff face
# and we want to process slices perpendicular to the face
# heightmap will have x,z (or z,x) index with highest ground level
if schema.Width > schema.Length:
heightmap = zeros((schema.Width, schema.Length), dtype='float32')
heightmap[coords[0], coords[1]] = coords[2]
newHeightmap = zeros((schema.Width, schema.Length), dtype='uint16')
slice_count = schema.Width
slice_width = schema.Length
else:
heightmap = zeros((schema.Length, schema.Width), dtype='float32')
heightmap[coords[1], coords[0]] = coords[2]
newHeightmap = zeros((schema.Length, schema.Width), dtype='uint16')
slice_count = schema.Length
slice_width = schema.Width
nonTerrainBlocks = ~terrainBlocks
nonTerrainBlocks &= schema.Blocks != 0
for slice_no in range(0, slice_count):
cliff_height = 0
# determine pos and height of cliff in this slice
for cur_pos in range(0, slice_width - 1):
if abs(heightmap[slice_no, cur_pos] - \
heightmap[slice_no, cur_pos + 1]) > abs(cliff_height):
cliff_height = \
heightmap[slice_no, cur_pos] - \
heightmap[slice_no, cur_pos + 1]
cliff_pos = cur_pos
if abs(cliff_height) < 2:
# nothing to adjust - just copy heightmap to newHightmap
adjheight(heightmap, newHeightmap, slice_no, 0, 1, 0, 1, 1, slice_width)
continue
# Try to keep adjusted columns within 1 column of their neighbours
# but ramp up to 4 blocks up/down on each column when needed
for max_step in range(1, 4):
can_left = maxadj(heightmap, slice_no, cliff_pos, -1, cliff_height < 0, max_step, slice_width)
can_right = maxadj(heightmap, slice_no, cliff_pos + 1, 1, cliff_height > 0, max_step, slice_width)
if can_right < 0 and RLOption == "Raise Only":
can_right = 0
if can_right > 0 and RLOption == "Lower Only":
can_right = 0
if can_left < 0 and RLOption == "Raise Only":
can_left = 0
if can_left > 0 and RLOption == "Lower Only":
can_left = 0
if cliff_height < 0 and can_right - can_left < cliff_height:
if abs(can_left) > abs(can_right):
adj_left = -1 * (cliff_height - max([int(cliff_height / 2), can_right]))
adj_right = cliff_height + adj_left
else:
adj_right = cliff_height - max([int(cliff_height / 2), -can_left])
adj_left = -1 * (cliff_height - adj_right + 1)
else:
if cliff_height > 0 and can_right - can_left > cliff_height:
if abs(can_left) > abs(can_right):
adj_left = -1 * (cliff_height - min([int(cliff_height / 2), can_right]))
adj_right = cliff_height + adj_left
else:
adj_right = cliff_height - min([int(cliff_height / 2), -can_left]) - 1
adj_left = -1 * (cliff_height - adj_right)
else:
adj_right = 0
adj_left = 0
continue
break
adjheight(heightmap, newHeightmap, slice_no, cliff_pos, -1, adj_left, can_left, max_step, slice_width)
adjheight(heightmap, newHeightmap, slice_no, cliff_pos + 1, 1, adj_right, can_right, max_step, slice_width)
# OK, newHeightMap has new height for each column
# so it's just a matter of moving everything up/down
for x, z in itertools.product(xrange(1, schema.Width - 1), xrange(1, schema.Length - 1)):
if schema.Width > schema.Length:
oh = heightmap[x, z]
nh = newHeightmap[x, z]
else:
oh = heightmap[z, x]
nh = newHeightmap[z, x]
delta = nh - oh
column = array(schema.Blocks[x, z])
# Keep bottom 5 blocks, so we don't loose bedrock
keep = min([5, nh])
Waterdepth = 0
# Detect Water on top
if column[oh + 1:oh + 2] == am.Water.ID or \
column[oh + 1:oh + 2] == am.Ice.ID:
for cur_pos in range(oh + 1, schema.Height):
if column[cur_pos:cur_pos + 1] != am.Water.ID and \
column[cur_pos:cur_pos + 1] != am.Ice.ID: break
Waterdepth = Waterdepth + 1
if delta == 0:
column[oh:] = schema.Blocks[x, z, oh:]
if delta < 0:
# Moving column down
column[keep:delta] = schema.Blocks[x, z, keep - delta:]
column[delta:] = am.Air.ID
if Waterdepth > 0:
# Avoid steping small lakes, etc on cliff top
# replace with dirt 'n grass
column[nh:nh + 1] = am.Grass.ID
column[nh + 1:nh + 1 + delta] = am.Air.ID
if delta > 0:
# Moving column up
column[keep + delta:] = schema.Blocks[x, z, keep:-delta]
# Put stone in gap at the bottom
column[keep:keep + delta] = am.Stone.ID
if Waterdepth > 0:
if Waterdepth > delta:
# Retain Ice
if column[nh + Waterdepth:nh + Waterdepth + 1] == am.Ice.ID:
column[nh + Waterdepth - delta:nh + 1 + Waterdepth - delta] = \
am.Ice.ID
column[nh + 1 + Waterdepth - delta:nh + 1 + Waterdepth] = am.Air.ID
else:
if Waterdepth < delta - 2:
column[nh:nh + 1] = am.Grass.ID
column[nh + 1:nh + 1 + Waterdepth] = am.Air.ID
else:
# Beach at the edge
column[nh - 4:nh - 2] = am.Sandstone.ID
column[nh - 2:nh + 1] = am.Sand.ID
column[nh + 1:nh + 1 + Waterdepth] = am.Air.ID
schema.Blocks[x, z] = column
level.copyBlocksFrom(schema, schema.bounds, box.origin)