Minecraft-Overviewer/overviewer_core/rendermodes.py

265 lines
8.4 KiB
Python

# This file is part of the Minecraft Overviewer.
#
# Minecraft Overviewer is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
#
# Minecraft Overviewer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
# Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
from datetime import datetime, timedelta
from PIL import Image
from . import textures
"""The contents of this file are imported into the namespace of config files.
It also defines the render primitive objects, which are used by the C code.
Each render primitive has a corresponding section of C code, so both places
must be changed simultaneously if you want to make any changes.
"""
class RenderPrimitive(object):
options = {}
name = None
def __init__(self, **kwargs):
if self.name is None:
raise RuntimeError("RenderPrimitive cannot be used directly")
self.option_values = {}
for key, val in kwargs.items():
if not key in self.options:
raise ValueError("primitive `{0}' has no option `{1}'".format(self.name, key))
self.option_values[key] = val
# set up defaults
for name, (description, default) in self.options.items():
if not name in self.option_values:
self.option_values[name] = default
class Base(RenderPrimitive):
name = "base"
options = {
"biomes": ("whether or not to use biomes", True),
}
class NetherOld(RenderPrimitive):
name = "netherold"
class Nether(RenderPrimitive):
name = "nether"
class HeightFading(RenderPrimitive):
name = "height-fading"
options = {
# 128 is *WRONG*, it should be 64. but we're grandfathered in for now
"sealevel": ("target sea level", 128),
}
black_color = Image.new("RGB", (24,24), (0,0,0))
white_color = Image.new("RGB", (24,24), (255,255,255))
class Depth(RenderPrimitive):
name = "depth"
options = {
"min": ("lowest level of blocks to render", -64),
"max": ("highest level of blocks to render", 319),
}
class Exposed(RenderPrimitive):
name = "exposed"
options = {
"mode": ("0 = exposed blocks only, 1 = unexposed blocks only", 0),
}
class NoFluids(RenderPrimitive):
name = "no-fluids"
class EdgeLines(RenderPrimitive):
name = "edge-lines"
options = {
"opacity": ("darkness of the edge lines, from 0.0 to 1.0", 0.15),
}
class Cave(RenderPrimitive):
name = "cave"
options = {
"only_lit": ("only render lit caves", False),
}
class DepthTinting(RenderPrimitive):
name = "depth-tinting"
@property
def depth_colors(self):
depth_colors = getattr(self, "_depth_colors", [])
if depth_colors:
return depth_colors
r = 255
g = 0
b = 0
for z in range(128):
depth_colors.append(r)
depth_colors.append(g)
depth_colors.append(b)
if z < 32:
g += 7
elif z < 64:
r -= 7
elif z < 96:
b += 7
else:
g -= 7
self._depth_colors = depth_colors
return depth_colors
class Lighting(RenderPrimitive):
name = "lighting"
options = {
"strength": ("how dark to make the shadows, from 0.0 to 1.0", 1.0),
"night": ("whether to use nighttime skylight settings", False),
"color": ("whether to use colored light", False),
}
@property
def facemasks(self):
facemasks = getattr(self, "_facemasks", None)
if facemasks:
return facemasks
white = Image.new("L", (24,24), 255)
top = Image.new("L", (24,24), 0)
left = Image.new("L", (24,24), 0)
whole = Image.new("L", (24,24), 0)
toppart = textures.Textures.transform_image_top(white)
leftpart = textures.Textures.transform_image_side(white)
# using the real PIL paste here (not alpha_over) because there is
# no alpha channel (and it's mode "L")
top.paste(toppart, (0,0))
left.paste(leftpart, (0,6))
right = left.transpose(Image.FLIP_LEFT_RIGHT)
# Manually touch up 6 pixels that leave a gap, like in
# textures._build_block()
for x,y in [(13,23), (17,21), (21,19)]:
right.putpixel((x,y), 255)
for x,y in [(3,4), (7,2), (11,0)]:
top.putpixel((x,y), 255)
# special fix for chunk boundary stipple
for x,y in [(13,11), (17,9), (21,7)]:
right.putpixel((x,y), 0)
self._facemasks = (top, left, right)
return self._facemasks
class SmoothLighting(Lighting):
name = "smooth-lighting"
class ClearBase(RenderPrimitive):
name = "clear-base"
class Overlay(RenderPrimitive):
name = "overlay"
options = {
'overlay_color' : ('a tuple of (r, g, b, a) for coloring the overlay', None),
}
@property
def whitecolor(self):
whitecolor = getattr(self, "_whitecolor", None)
if whitecolor:
return whitecolor
white = Image.new("RGBA", (24,24), (255, 255, 255, 255))
self._whitecolor = white
return white
@property
def facemask_top(self):
facemask_top = getattr(self, "_facemask_top", None)
if facemask_top:
return facemask_top
white = Image.new("L", (24,24), 255)
top = Image.new("L", (24,24), 0)
toppart = textures.Textures.transform_image_top(white)
top.paste(toppart, (0,0))
for x,y in [(3,4), (7,2), (11,0)]:
top.putpixel((x,y), 255)
self._facemask_top = top
return top
class SpawnOverlay(Overlay):
name = "overlay-spawn"
class SlimeOverlay(Overlay):
name = "overlay-slime"
class StructureOverlay(Overlay):
name = "overlay-structure"
options = {
'structures': ('a list of ((((relx, rely, relz), blockid), ...), (r, g, b, a)) tuples for coloring minerals',
[(((0, 0, 0, 66), (0, -1, 0, 4)), (255, 0, 0, 255)),
(((0, 0, 0, 27), (0, -1, 0, 4)), (0, 255, 0, 255)),
(((0, 0, 0, 28), (0, -1, 0, 4)), (255, 255, 0, 255)),
(((0, 0, 0, 157), (0, -1, 0, 4)), (255, 100, 0, 255)),
]),
}
class MineralOverlay(Overlay):
name = "overlay-mineral"
options = {
'minerals' : ('a list of (blockid, (r, g, b)) tuples for coloring minerals', None),
}
class BiomeOverlay(Overlay):
name = "overlay-biomes"
options = {
'biomes' : ('a list of (biome, (r, g, b)) tuples for coloring biomes', None),
'alpha' : ('an integer value between 0 (transparent) and 255 (opaque)', None),
}
class HeatmapOverlay(Overlay):
t_now = datetime.now()
name = "overlay-heatmap"
options = {
't_invisible': (
'the timestamp when the overlay will get invisible (e.g. 1 month go)',
int((t_now - timedelta(days=30)).timestamp())
),
't_full': ('the timestamp when the overlay will be fully visible (e.g. now)', int(t_now.timestamp())),
}
class Hide(RenderPrimitive):
name = "hide"
options = {
'blocks' : ('a list of blockids or (blockid, data) tuples of blocks to hide', []),
}
# Built-in rendermodes for your convenience!
normal = [Base(), EdgeLines()]
lighting = [Base(), EdgeLines(), Lighting()]
smooth_lighting = [Base(), EdgeLines(), SmoothLighting()]
night = [Base(), EdgeLines(), Lighting(night=True)]
smooth_night = [Base(), EdgeLines(), SmoothLighting(night=True)]
netherold = [Base(), EdgeLines(), NetherOld()]
netherold_lighting = [Base(), EdgeLines(), NetherOld(), Lighting()]
netherold_smooth_lighting = [Base(), EdgeLines(), NetherOld(), SmoothLighting()]
nether = [Base(), EdgeLines(), Nether()]
nether_lighting = [Base(), EdgeLines(), Nether(), Lighting()]
nether_smooth_lighting = [Base(), EdgeLines(), Nether(), SmoothLighting()]
cave = [Base(), EdgeLines(), Cave(), DepthTinting()]