A usecase to demonstrate a possible application of the extended functionality:
**"Rails Overlay that draws only the rails that are on Cobblestone for a subway map."**
With this patch it is very easy to achive that:
```python
MineralOverlay(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))])
```
In this case the overlay will be red for rails on cobblestone and green for powerrails on cobblestone.
The syntax is `(<tuple of conditions>, <target color>)`
* where `<target color>` is a 4 tuple with a `(r, g, b, a)` color
* and `<tuple of conditions>` is a tuple with an arbitrary number of conditions with the following syntax:
`((relx, rely, relz, blkid), ...)` where the `rel<>` parameters specify the relative coordinates to the block that is checked if it matches bklid.
In the example the fist tuple `(0,0,0,66)` checks if at the current position is a
rail while `(0,-1,0,4)` checks if at one below the current position is a cobblestone.
If both are true then the color `(255, 0, 0, 255)` is used.
A Sample Config file exploiting the capabilities:
``` python
worlds['My World'] = "~/.minecraft/saves/test/"
outputdir = "/tmp/test_render"
rendermode = "lighting"
renders["render1"] = {
'world': 'My World',
'title': 'A regular render',
}
renders["render_overlay_dafault_rails"] = {
'world': 'My World',
'title': 'Default Rails',
'rendermode': [ClearBase(), StructureOverlay()],
'overlay': ['render1'],
}
renders["render_overlay_cust_rails"] = {
'world': 'My World',
'title': 'Custom Rails',
#relative coordinates [[(relx, rely, relz, mineral)], (red, green, blue, alpha)]
'rendermode': [ClearBase(), StructureOverlay(structures=[(((0, 0, 0, 66), (0, -1, 0, 4)), (255, 0, 0, 255)),
(((0, 0, 0, 27), (0, -1, 0, 4)), (0, 255, 0, 255))])],
'overlay': ['render1'],
}
```
The "Default Rails" overlay uses default coloring of the structures overlay. "Custom Rails" uses some custom coloring.
fixes overviewer/Minecraft-Overviewer#556 and fixes overviewer/Minecraft-Overviewer#787
253 lines
8.0 KiB
Python
253 lines
8.0 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 PIL import Image
|
|
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.iteritems():
|
|
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.iteritems():
|
|
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", 0),
|
|
"max": ("highest level of blocks to render", 255),
|
|
}
|
|
|
|
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 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()]
|