Merge branch 'master' into py-package
Conflicts: overviewer_core/data/config.js overviewer_core/data/web_assets/functions.js overviewer_core/data/web_assets/style.css setup.py web_assets/overviewer.css web_assets/style.css
This commit is contained in:
53
CONTRIBUTORS.rst
Normal file
53
CONTRIBUTORS.rst
Normal file
@@ -0,0 +1,53 @@
|
||||
============
|
||||
Contributors
|
||||
============
|
||||
|
||||
This file contains a list of every person who has contributed code to
|
||||
Overviewer. It was created from the git commit log, and should include
|
||||
everyone, but we may have missed a few.
|
||||
|
||||
Not currently included (but hopefully soon) are countless testers and bug
|
||||
reporters that helped fixed the many bugs that have popped up in the course of
|
||||
development.
|
||||
|
||||
---------------
|
||||
Original Author
|
||||
---------------
|
||||
|
||||
* Andrew Brown <brownan@gmail.com>
|
||||
|
||||
-------------------------
|
||||
Long-term Contributions
|
||||
-------------------------
|
||||
|
||||
These contributors have made many changes, over a fairly long time span, or
|
||||
for many different parts of the code.
|
||||
|
||||
* Alejandro Aguilera <fenixin@lavabit.com>
|
||||
* Andrew Chin <achin@eminence32.net>
|
||||
* Aaron Griffith <aargri@gmail.com>
|
||||
* Alex Headley <aheadley@waysaboutstuff.com>
|
||||
* Alex Jurkiewicz <alex@bluebottle.net.au>
|
||||
* Xon <Xon@localhost>
|
||||
|
||||
------------------------
|
||||
Short-term Contributions
|
||||
------------------------
|
||||
|
||||
These contributors have made specific changes for a particular bug fix or
|
||||
feature.
|
||||
|
||||
* arrai <array.of.intellect@gmail.com>
|
||||
* Kyle Brantley <kyle@averageurl.com>
|
||||
* cbarber <CraigBarber@taryx.com>
|
||||
* Alex Cline <cline@vivisimo.com>
|
||||
* Stephen Fluin <stephen@mistuph.com>
|
||||
* Benjamin Herr <ben@0x539.de>
|
||||
* Ryan Hitchman <hitchmanr@gmail.com>
|
||||
* Jenny <jennytoo@gmail.com>
|
||||
* Michael Jensen <emjay1988@gmail.com>
|
||||
* Ryan McCue <ryanmccue@cubegames.net>
|
||||
* Morlok8k <otis.spankmeyer@gmail.com>
|
||||
* Gregory Short <gshort2@gmail.com>
|
||||
* Sam Steele <sam@sigbox.c99.org>
|
||||
* timwolla <timwolla@mail.develfusion.com>
|
||||
140
README.rst
140
README.rst
@@ -1,21 +1,26 @@
|
||||
====================
|
||||
Minecraft Overviewer
|
||||
====================
|
||||
By Andrew Brown and contributors
|
||||
By Andrew Brown and contributors (see CONTRIBUTORS.rst).
|
||||
|
||||
http://github.com/brownan/Minecraft-Overviewer
|
||||
|
||||
Generates large resolution images of a Minecraft map.
|
||||
|
||||
In short, this program reads in Minecraft world files and renders very large
|
||||
resolution images. It performs a similar function to the existing Minecraft
|
||||
Cartographer program but with a slightly different goal in mind: to generate
|
||||
large resolution images such that one can zoom in and see details.
|
||||
resolution images that can be viewed through a Google Maps interface. It
|
||||
performs a similar function to the existing Minecraft Cartographer program but
|
||||
with a slightly different goal in mind: to generate large resolution images
|
||||
such that one can zoom in and see details.
|
||||
|
||||
See some examples here!
|
||||
http://github.com/brownan/Minecraft-Overviewer/wiki/Map-examples
|
||||
|
||||
(To contact me, send me a message on Github)
|
||||
Further documentation may be found at
|
||||
https://github.com/brownan/Minecraft-Overviewer/wiki/Documentation
|
||||
|
||||
To contact the developers and other users, go to the site at the top of this
|
||||
README, or go to #overviewer on irc.freenode.net.
|
||||
|
||||
Features
|
||||
========
|
||||
@@ -56,6 +61,12 @@ If something doesn't work, let me know.
|
||||
Using the Overviewer
|
||||
====================
|
||||
|
||||
For a quick-start guide, see
|
||||
https://github.com/brownan/Minecraft-Overviewer/wiki/Quick-Start-Guide
|
||||
|
||||
If you are upgrading from an older Overviewer to the new DTT code, see
|
||||
https://github.com/brownan/Minecraft-Overviewer/wiki/DTT-Upgrade-Guide
|
||||
|
||||
Disclaimers
|
||||
-----------
|
||||
Before you dive into using this, just be aware that, for large maps, there is a
|
||||
@@ -89,7 +100,7 @@ you can use the Overviewer:
|
||||
texture packs out there.
|
||||
|
||||
Biome Tinting
|
||||
~~~~~~~~~~~~~
|
||||
-------------
|
||||
With the Halloween update, biomes were added to Minecraft. In order to get
|
||||
biome-accurate tinting, the Overviewer can use biome data produced by the
|
||||
Minecraft Biome Extractor tool. This tool can be downloaded from:
|
||||
@@ -102,12 +113,12 @@ then the Overviewer will use a static tinting for grass and leaves.
|
||||
|
||||
Compiling the C Extension
|
||||
-------------------------
|
||||
The C Extension for Overviewer is no longer optional. In addition to providing
|
||||
a higher quality image compositing function that looks better on maps with lighting
|
||||
enabled, it now does the bulk of the rendering.
|
||||
The C Extension for Overviewer is no longer optional. In addition to
|
||||
providing a higher quality image compositing function that looks better on
|
||||
maps with lighting enabled, it now does the bulk of the rendering.
|
||||
|
||||
If you downloaded Overviewer as a binary package, this extension will already be
|
||||
compiled for you.
|
||||
If you downloaded Overviewer as a binary package, this extension will already
|
||||
be compiled for you.
|
||||
|
||||
If you have a C compiler and the Python development libraries set up, you can
|
||||
compile this extension like this::
|
||||
@@ -119,7 +130,7 @@ look for a package named 'python-dev', 'python-devel' or similar. Also, some
|
||||
Python distributions do not install "Imaging.h" and "ImPlatform.h" properly. If
|
||||
you get errors complaining about them, you can get them from the PIL source, or
|
||||
at <http://svn.effbot.org/public/tags/pil-1.1.7/libImaging/>. Just put them in
|
||||
the same directory as "_composite.c".
|
||||
the same directory as "overviewer.py".
|
||||
|
||||
For more detailed instructions, check the wiki:
|
||||
https://github.com/brownan/Minecraft-Overviewer/wiki/Build-Instructions
|
||||
@@ -142,12 +153,6 @@ Options
|
||||
-h, --help
|
||||
Shows the list of options and exits
|
||||
|
||||
--imgformat=FORMAT
|
||||
Set the output image format used for the tiles. The default is 'png',
|
||||
but 'jpg' is also supported. Note that regardless of what you choose,
|
||||
Overviewer will still use PNG for cached images to avoid recompression
|
||||
artifacts.
|
||||
|
||||
-p PROCS, --processes=PROCS
|
||||
Adding the "-p" option will utilize more cores during processing. This
|
||||
can speed up rendering quite a bit. The default is set to the same
|
||||
@@ -157,34 +162,6 @@ Options
|
||||
|
||||
python overviewer.py -p 5 <Path to World> <Output Directory>
|
||||
|
||||
-z ZOOM, --zoom=ZOOM
|
||||
The Overviewer by default will detect how many zoom levels are required
|
||||
to show your entire map. This option sets it manually.
|
||||
|
||||
*You do not normally need to set this option!*
|
||||
|
||||
This is equivalent to setting the dimensions of the highest zoom level. It
|
||||
does not actually change how the map is rendered, but rather *how much of
|
||||
the map is rendered.* (Calling this option "zoom" may be a bit misleading,
|
||||
I know)
|
||||
|
||||
To be precise, it sets the width and height of the highest zoom level, in
|
||||
tiles. A zoom level of z means the highest zoom level of your map will be
|
||||
2^z by 2^z tiles.
|
||||
|
||||
This option map be useful if you have some outlier chunks causing your map
|
||||
to be too large, or you want to render a smaller portion of your map,
|
||||
instead of rendering everything.
|
||||
|
||||
This will render your map with 7 zoom levels::
|
||||
|
||||
python overviewer.py -z 7 <Path to World> <Output Directory>
|
||||
|
||||
Remember that each additional zoom level adds 4 times as many tiles as
|
||||
the last. This can add up fast, zoom level 10 has over a million tiles.
|
||||
Tiles with no content will not be rendered, but they still take a small
|
||||
amount of time to process.
|
||||
|
||||
-d, --delete
|
||||
This option changes the mode of execution. No tiles are rendered, and
|
||||
instead, files are deleted.
|
||||
@@ -204,38 +181,65 @@ Options
|
||||
a certain date. Or perhaps you can incrementally update your map by passing
|
||||
in a subset of regions each time. It's up to you!
|
||||
|
||||
--lighting
|
||||
This option enables map lighting, using lighting information stored by
|
||||
Minecraft inside the chunks. This will make your map prettier, at the cost
|
||||
of update speed.
|
||||
--rendermodes=MODE1[,MODE2,...]
|
||||
Use this option to specify which render mode to use, such as lighting or
|
||||
night. Use --list-rendermodes to get a list of available rendermodes, and
|
||||
a short description of each. If you provide more than one mode (separated
|
||||
by commas), Overviewer will render all of them at once, and provide a
|
||||
toggle on the resulting map to switch between them.
|
||||
|
||||
Note that for existing, unlit maps, you may want to clear your cache
|
||||
(with -d) before updating the map to use lighting. Otherwise, only updated
|
||||
chunks will have lighting enabled.
|
||||
|
||||
--night
|
||||
This option enables --lighting, and renders the world at night.
|
||||
|
||||
--web-assets-hook=HOOK
|
||||
This option lets you specify a script to run after the web assets have been
|
||||
copied into the output directory, but before any tile rendering takes
|
||||
place. This is an ideal time to do any custom postprocessing for markers.js
|
||||
or other web assets.
|
||||
|
||||
The script should be executable, and it should accept one argument:
|
||||
the path to the output directory.
|
||||
--list-rendermodes
|
||||
List the available render modes, and a short description of each.
|
||||
|
||||
|
||||
Settings
|
||||
--------
|
||||
|
||||
You can optionally store settings in a file named settings.py. It is a regular
|
||||
python script, so you can use any python functions or modules you want.
|
||||
|
||||
This section needs to be expanded
|
||||
For a sample settings file, look at 'sample.settings.py'. Note that this file
|
||||
is not meant to be used directly, but instead it should be used as a
|
||||
collection of examples to guide writing your own.
|
||||
|
||||
For a sample settings file, look at sample.settings.py
|
||||
Here's a (possibly incomplete) list of available settings, which are available
|
||||
in settings.py. Note that you can also set command-line options in a similar
|
||||
way.
|
||||
|
||||
imgformat=FORMAT
|
||||
Set the output image format used for the tiles. The default is 'png',
|
||||
but 'jpg' is also supported.
|
||||
|
||||
zoom=ZOOM
|
||||
The Overviewer by default will detect how many zoom levels are required
|
||||
to show your entire map. This option sets it manually.
|
||||
|
||||
*You do not normally need to set this option!*
|
||||
|
||||
This is equivalent to setting the dimensions of the highest zoom level. It
|
||||
does not actually change how the map is rendered, but rather *how much of
|
||||
the map is rendered.* (Calling this option "zoom" may be a bit misleading,
|
||||
I know)
|
||||
|
||||
To be precise, it sets the width and height of the highest zoom level, in
|
||||
tiles. A zoom level of z means the highest zoom level of your map will be
|
||||
2^z by 2^z tiles.
|
||||
|
||||
This option map be useful if you have some outlier chunks causing your map
|
||||
to be too large, or you want to render a smaller portion of your map,
|
||||
instead of rendering everything.
|
||||
|
||||
Remember that each additional zoom level adds 4 times as many tiles as
|
||||
the last. This can add up fast, zoom level 10 has over a million tiles.
|
||||
Tiles with no content will not be rendered, but they still take a small
|
||||
amount of time to process.
|
||||
|
||||
web_assets_hook
|
||||
This option lets you define a function to run after the web assets have
|
||||
been copied into the output directory, but before any tile rendering takes
|
||||
place. This is an ideal time to do any custom postprocessing for
|
||||
markers.js or other web assets.
|
||||
|
||||
This function should accept one argument: a QuadtreeGen object.
|
||||
|
||||
Viewing the Results
|
||||
-------------------
|
||||
|
||||
@@ -96,7 +96,9 @@ def main():
|
||||
parser.add_option("--chunklist", dest="chunklist", help="A file containing, on each line, a path to a chunkfile to update. Instead of scanning the world directory for chunks, it will just use this list. Normal caching rules still apply.")
|
||||
parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render types, separated by commas. Use --list-rendermodes to list them all.", type="choice", choices=avail_rendermodes, required=True, default=avail_rendermodes[0], listify=True)
|
||||
parser.add_option("--list-rendermodes", dest="list_rendermodes", action="store_true", help="List available render modes and exit.", commandLineOnly=True)
|
||||
parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.", configFileOnly=True )
|
||||
parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg.", configFileOnly=True )
|
||||
parser.add_option("--imgquality", dest="imgquality", default=95, help="Specify the quality of image output when using imgformat=\"jpg\".", type="int", configFileOnly=True)
|
||||
parser.add_option("--bg_color", dest="bg_color", help="Configures the background color for the GoogleMap output. Specify in #RRGGBB format", configFileOnly=True, type="string", default="#1A1A1A")
|
||||
parser.add_option("--optimize-img", dest="optimizeimg", help="If using png, perform image file size optimizations on the output. Specify 1 for pngcrush, 2 for pngcrush+optipng+advdef. This may double (or more) render times, but will produce up to 30% smaller images. NOTE: requires corresponding programs in $PATH or %PATH%", configFileOnly=True)
|
||||
parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a QuadtreeGen object as its only argument.", action="store", metavar="SCRIPT", type="function", configFileOnly=True)
|
||||
parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.")
|
||||
@@ -162,10 +164,18 @@ def main():
|
||||
logging.error("Invalid world number")
|
||||
sys.exit(1)
|
||||
|
||||
if len(args) != 2:
|
||||
if len(args) < 2:
|
||||
if options.delete:
|
||||
return delete_all(worlddir, None)
|
||||
parser.error("Where do you want to save the tiles?")
|
||||
logging.error("Where do you want to save the tiles?")
|
||||
sys.exit(1)
|
||||
elif len(args) > 2:
|
||||
if options.delete:
|
||||
return delete_all(worlddir, None)
|
||||
parser.print_help()
|
||||
logging.error("Sorry, you specified too many arguments")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
destdir = args[1]
|
||||
if options.display_config:
|
||||
@@ -214,10 +224,12 @@ def main():
|
||||
|
||||
logging.info("Rending the following tilesets: %s", ",".join(options.rendermode))
|
||||
|
||||
bgcolor = (int(options.bg_color[1:3],16), int(options.bg_color[3:5],16), int(options.bg_color[5:7],16), 0)
|
||||
|
||||
# create the quadtrees
|
||||
# TODO chunklist
|
||||
q = []
|
||||
qtree_args = {'depth' : options.zoom, 'imgformat' : imgformat, 'optimizeimg' : optimizeimg}
|
||||
qtree_args = {'depth' : options.zoom, 'imgformat' : imgformat, 'imgquality' : options.imgquality, 'optimizeimg' : optimizeimg, 'bgcolor' : bgcolor}
|
||||
for rendermode in options.rendermode:
|
||||
if rendermode == 'normal':
|
||||
qtree = quadtree.QuadtreeGen(w, destdir, rendermode=rendermode, tiledir='tiles', **qtree_args)
|
||||
@@ -225,16 +237,21 @@ def main():
|
||||
qtree = quadtree.QuadtreeGen(w, destdir, rendermode=rendermode, **qtree_args)
|
||||
q.append(qtree)
|
||||
|
||||
# do quadtree-level preprocessing
|
||||
for qtree in q:
|
||||
qtree.go(options.procs)
|
||||
|
||||
# create the distributed render
|
||||
r = rendernode.RenderNode(q)
|
||||
|
||||
# write out the map and web assets
|
||||
m = googlemap.MapGen(q, skipjs=options.skipjs, web_assets_hook=options.web_assets_hook)
|
||||
m = googlemap.MapGen(q, configInfo=options)
|
||||
m.go(options.procs)
|
||||
|
||||
# render the tiles!
|
||||
r.go(options.procs)
|
||||
|
||||
# finish up the map
|
||||
m.finalize()
|
||||
|
||||
|
||||
|
||||
@@ -114,13 +114,15 @@ def get_tileentity_data(level):
|
||||
return data
|
||||
|
||||
# This set holds blocks ids that can be seen through, for occlusion calculations
|
||||
transparent_blocks = set([0, 6, 8, 9, 18, 20, 37, 38, 39, 40, 44, 50, 51, 52, 53, 55,
|
||||
59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85, 92])
|
||||
transparent_blocks = set([ 0, 6, 8, 9, 18, 20, 26, 27, 28, 30, 37, 38, 39, 40,
|
||||
44, 50, 51, 52, 53, 55, 59, 63, 64, 65, 66, 67, 68, 69,
|
||||
70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85, 90, 92,
|
||||
93, 94])
|
||||
|
||||
# This set holds block ids that are solid blocks
|
||||
solid_blocks = set([1, 2, 3, 4, 5, 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
|
||||
23, 24, 25, 35, 41, 42, 43, 44, 45, 46, 47, 48, 49, 53, 54, 56, 57, 58, 60,
|
||||
61, 62, 64, 65, 66, 67, 71, 73, 74, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 91, 92])
|
||||
61, 62, 67, 73, 74, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 91])
|
||||
|
||||
# This set holds block ids that are fluid blocks
|
||||
fluid_blocks = set([8,9,10,11])
|
||||
@@ -285,6 +287,13 @@ class ChunkRenderer(object):
|
||||
return self._up_right_blocks
|
||||
up_right_blocks = property(_load_up_right_blocks)
|
||||
|
||||
def _load_up_right_skylight(self):
|
||||
"""Loads and returns lower-right skylight array"""
|
||||
if not hasattr(self, "_up_right_skylight"):
|
||||
self._load_up_right()
|
||||
return self._up_right_skylight
|
||||
up_right_skylight = property(_load_up_right_skylight)
|
||||
|
||||
def _load_up_left(self):
|
||||
"""Loads and sets data from upper-left chunk"""
|
||||
chunk_path = self.world.get_region_path(self.chunkX, self.chunkY - 1)
|
||||
@@ -305,6 +314,13 @@ class ChunkRenderer(object):
|
||||
return self._up_left_blocks
|
||||
up_left_blocks = property(_load_up_left_blocks)
|
||||
|
||||
def _load_up_left_skylight(self):
|
||||
"""Loads and returns lower-right skylight array"""
|
||||
if not hasattr(self, "_up_left_skylight"):
|
||||
self._load_up_left()
|
||||
return self._up_left_skylight
|
||||
up_left_skylight = property(_load_up_left_skylight)
|
||||
|
||||
def generate_pseudo_ancildata(self,x,y,z,blockid, north_position = 0 ):
|
||||
""" Generates a pseudo ancillary data for blocks that depend of
|
||||
what are surrounded and don't have ancillary data
|
||||
@@ -449,7 +465,7 @@ def generate_facemasks():
|
||||
return (top, left, right)
|
||||
facemasks = generate_facemasks()
|
||||
black_color = Image.new("RGB", (24,24), (0,0,0))
|
||||
red_color = Image.new("RGB", (24,24), (229,36,38))
|
||||
white_color = Image.new("RGB", (24,24), (255,255,255))
|
||||
|
||||
# Render 128 different color images for color coded depth blending in cave mode
|
||||
def generate_depthcolors():
|
||||
@@ -458,8 +474,10 @@ def generate_depthcolors():
|
||||
g = 0
|
||||
b = 0
|
||||
for z in range(128):
|
||||
img = Image.new("RGB", (24,24), (r,g,b))
|
||||
depth_colors.append(img)
|
||||
depth_colors.append(r)
|
||||
depth_colors.append(g)
|
||||
depth_colors.append(b)
|
||||
|
||||
if z < 32:
|
||||
g += 7
|
||||
elif z < 64:
|
||||
|
||||
@@ -4,7 +4,8 @@ import os.path
|
||||
import logging
|
||||
|
||||
class OptionsResults(object):
|
||||
pass
|
||||
def get(self, *args):
|
||||
return self.__dict__.get(*args)
|
||||
|
||||
class ConfigOptionParser(object):
|
||||
def __init__(self, **kwargs):
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
|
||||
var config = {
|
||||
fileExt: '{imgformat}',
|
||||
tileSize: 384,
|
||||
defaultZoom: 2,
|
||||
maxZoom: {maxzoom},
|
||||
// center on this point, in world coordinates, ex:
|
||||
//center: [0,0,0],
|
||||
center: {spawn_coords},
|
||||
cacheMinutes: 0, // Change this to have browsers automatically request new images every x minutes
|
||||
bg_color: '#1A1A1A',
|
||||
debug: false
|
||||
};
|
||||
|
||||
|
||||
/* signGroups -- A list of signpost groups. A signpost can fall into zero, one, or more than one
|
||||
* group. See below for some examples.
|
||||
*
|
||||
* Required:
|
||||
* label : string. Displayed in the drop down menu control.
|
||||
* match : function. Applied to each marker (from markers.js). It is returns true if the marker
|
||||
* Should be part of the group.
|
||||
*
|
||||
* Optional:
|
||||
* checked : boolean. Set to true to have the group visible by default
|
||||
* icon : string. Used to specify an icon url.
|
||||
*/
|
||||
var signGroups = [
|
||||
// {label: "'To'", checked: false, match: function(s) {return s.msg.match(/to/)}},
|
||||
// {label: "Storage", match: function(s) {return s.msg.match(/storage/i) || s.msg.match(/dirt/i) || s.msg.match(/sand/)}},
|
||||
// {label: "Below Sealevel", match: function(s) { return s.y<64;}},
|
||||
// {label: "Info", match: function(s) { return s.msg.match("\\[info\\]");}, icon:"http://google-maps-icons.googlecode.com/files/info.png"},
|
||||
{label: "All", match: function(s) {return true}},
|
||||
];
|
||||
|
||||
/* mapTypeData -- a list of alternate map renderings available. At least one rendering must be
|
||||
* listed. When more than one are provided, controls to switch between them are provided, with
|
||||
* the first one being the default.
|
||||
*
|
||||
* Required:
|
||||
* label : string. Displayed on the control.
|
||||
* path : string. Location of the rendered tiles.
|
||||
* Optional:
|
||||
* base : string. Base of the url path for tile locations, useful for serving tiles from a different server than the js/html server.
|
||||
|
||||
var mapTypeData=[
|
||||
{'label': 'Unlit', 'path': 'tiles'},
|
||||
// {'label': 'Day', 'path': 'lighting/tiles'},
|
||||
// {'label': 'Night', 'path': 'night/tiles'},
|
||||
// {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'}
|
||||
];
|
||||
*/
|
||||
|
||||
var mapTypeData = {maptypedata};
|
||||
|
||||
157
overviewer_core/data/overviewerConfig.js
Normal file
157
overviewer_core/data/overviewerConfig.js
Normal file
@@ -0,0 +1,157 @@
|
||||
var overviewerConfig = {
|
||||
/**
|
||||
* These are things that will probably not need to be changed by the user,
|
||||
* but are there because otherwise changing them is a giant PITA.
|
||||
*/
|
||||
'CONST': {
|
||||
/**
|
||||
* Height and width of the tiles in pixels (I think).
|
||||
*/
|
||||
'tileSize': 384,
|
||||
/**
|
||||
* Various images used for markers and stuff.
|
||||
*/
|
||||
'image': {
|
||||
'defaultMarker': 'signpost.png',
|
||||
'signMarker': 'signpost_icon.png',
|
||||
'compass': 'compass.png',
|
||||
'spawnMarker': 'http://google-maps-icons.googlecode.com/files/home.png',
|
||||
'queryMarker': 'http://google-maps-icons.googlecode.com/files/regroup.png'
|
||||
},
|
||||
'mapDivId': 'mcmap',
|
||||
'regionStrokeWeight': 2
|
||||
},
|
||||
/**
|
||||
* General map settings.
|
||||
*/
|
||||
'map': {
|
||||
/**
|
||||
* Control the visibility of various controls.
|
||||
*/
|
||||
'controls': {
|
||||
/**
|
||||
* Pan control is the hand with the arrows around it in the upper left.
|
||||
*/
|
||||
'pan': true,
|
||||
/**
|
||||
* Zoom control is the zoom slider bar in the upper left.
|
||||
*/
|
||||
'zoom': true,
|
||||
/**
|
||||
* Spawn control is the "Spawn" button that centers the map on spawn.
|
||||
*/
|
||||
'spawn': true,
|
||||
/**
|
||||
* The compass in the upper right.
|
||||
*/
|
||||
'compass': true,
|
||||
/**
|
||||
* The mapType control is the slider for selecting different map types.
|
||||
*/
|
||||
'mapType': true,
|
||||
/**
|
||||
* The small box at the bottom that displays the link to the current map view.
|
||||
*/
|
||||
'link': true
|
||||
},
|
||||
/**
|
||||
* The zoom level when the page is loaded without a specific zoom setting
|
||||
*/
|
||||
'defaultZoom': 0,
|
||||
/**
|
||||
* This controls how far you can zoom out.
|
||||
*/
|
||||
'minZoom': {minzoom},
|
||||
/**
|
||||
* This controls how close you can zoom in.
|
||||
*/
|
||||
'maxZoom': {maxzoom},
|
||||
/**
|
||||
* Center on this point, in world coordinates. Should be an array, ex:
|
||||
* [0,0,0]
|
||||
*/
|
||||
'center': {spawn_coords},
|
||||
/**
|
||||
* Set this to tell browsers how long they should cache tiles in minutes.
|
||||
*/
|
||||
'cacheMinutes': 0,
|
||||
/**
|
||||
* Set to true to turn on debug mode, which adds a grid to the map along
|
||||
* with co-ordinates and a bunch of console output.
|
||||
*/
|
||||
'debug': false,
|
||||
},
|
||||
/**
|
||||
* Group definitions for objects that are partially selectable (signs and
|
||||
* regions).
|
||||
*/
|
||||
'objectGroups': {
|
||||
/* signs -- A list of signpost groups. A signpost can fall into zero,
|
||||
* one, or more than one group. See below for some examples.
|
||||
*
|
||||
* Required:
|
||||
* label : string. Displayed in the drop down menu control.
|
||||
* match : function. Applied to each marker (from markers.js). It
|
||||
* is returns true if the marker should be part
|
||||
* of the group.
|
||||
*
|
||||
* Optional:
|
||||
* checked : boolean. Set to true to have the group visible by default
|
||||
* icon : string. Used to specify an icon url.
|
||||
*/
|
||||
'signs': [
|
||||
//{label: "'To'", checked: false, match: function(s) {return s.msg.match(/to/)}},
|
||||
//{label: "Storage", match: function(s) {return s.msg.match(/storage/i) || s.msg.match(/dirt/i) || s.msg.match(/sand/)}},
|
||||
//{label: "Below Sealevel", match: function(s) { return s.y<64;}},
|
||||
//{label: "Info", match: function(s) { return s.msg.match("\\[info\\]");}, icon:"http://google-maps-icons.googlecode.com/files/info.png"},
|
||||
{'label':'All', 'match':function(sign){return true;}}
|
||||
],
|
||||
/* regions -- A list of region groups. A region can fall into zero,
|
||||
* one, or more than one group. See below for some examples.
|
||||
* Regions have been designed to work with the WorldGuard Overviewer
|
||||
* Region importer at @link http://goo.gl/dc0tV but your
|
||||
* host must support php in order to run WG2OvR. You can also continue
|
||||
* to use any other region format.
|
||||
*
|
||||
* Required:
|
||||
* label : string. Displayed in the drop down menu control.
|
||||
* clickable : boolean. Will determine if we should generate an
|
||||
* experimental info window that shows details
|
||||
* about the clicked region.
|
||||
* NOTE: if a region (as defined in region.js)
|
||||
* does not have a label, this will default to
|
||||
* false.
|
||||
* match : function. Applied to each region (from region.js). It
|
||||
* returns true if the region should be part of
|
||||
* the group.
|
||||
*
|
||||
* Optional:
|
||||
* checked : boolean. Set to true to have the group visible by default
|
||||
*/
|
||||
'regions': [
|
||||
//{'label':'All','clickable':true,'match':function(region){return true;}}
|
||||
]
|
||||
},
|
||||
/* mapTypes -- a list of alternate map renderings available. At least one
|
||||
* rendering must be listed. When more than one are provided, controls to
|
||||
* switch between them are provided, with the first one being the default.
|
||||
*
|
||||
* Required:
|
||||
* label : string. Displayed on the control.
|
||||
* path : string. Location of the rendered tiles.
|
||||
* Optional:
|
||||
* base : string. Base of the url path for tile locations, useful
|
||||
* for serving tiles from a different server than
|
||||
* the js/html server.
|
||||
* imgformat : string. File extension used for these tiles. Defaults to png.
|
||||
* overlay : bool. If true, this tile set will be treated like an overlay
|
||||
* Example:
|
||||
* 'mapTypes': [
|
||||
* {'label': 'Day', 'path': 'lighting/tiles'},
|
||||
* {'label': 'Night', 'path': 'night/tiles', 'imgformat': 'jpg'},
|
||||
* {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'},
|
||||
* {'label': 'Overlay', 'path': 'overlay/tiles', 'overlay': true}
|
||||
* ]
|
||||
*/
|
||||
'mapTypes': {maptypedata}
|
||||
};
|
||||
BIN
overviewer_core/data/textures/portal.png
Normal file
BIN
overviewer_core/data/textures/portal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 672 B |
@@ -1,468 +0,0 @@
|
||||
var markerCollection = {}; // holds groups of markers
|
||||
|
||||
var map;
|
||||
|
||||
var markersInit = false;
|
||||
var regionsInit = false;
|
||||
|
||||
var prevInfoWindow = null;
|
||||
|
||||
function prepareSignMarker(marker, item) {
|
||||
|
||||
var c = "<div class=\"infoWindow\"><img src=\"signpost.png\" /><p>" + item.msg.replace(/\n/g,"<br/>") + "</p></div>";
|
||||
var infowindow = new google.maps.InfoWindow({content: c
|
||||
});
|
||||
google.maps.event.addListener(marker, 'click', function() {
|
||||
if (prevInfoWindow)
|
||||
prevInfoWindow.close()
|
||||
infowindow.open(map,marker);
|
||||
prevInfoWindow = infowindow
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
function drawMapControls() {
|
||||
|
||||
// viewstate link
|
||||
var viewStateDiv = document.createElement('DIV');
|
||||
|
||||
//<div id="link" style="border:1px solid black;background-color:white;color:black;position:absolute;top:5px;right:5px"></div>
|
||||
|
||||
viewStateDiv.id="link";
|
||||
|
||||
|
||||
map.controls[google.maps.ControlPosition.BOTTOM_RIGHT].push(viewStateDiv);
|
||||
|
||||
|
||||
// compass rose, in the top right corner
|
||||
var compassDiv = document.createElement('DIV');
|
||||
|
||||
compassDiv.style.padding = '5px';
|
||||
|
||||
var compassImg = document.createElement('IMG');
|
||||
compassImg.src="compass.png";
|
||||
compassDiv.appendChild(compassImg);
|
||||
|
||||
compassDiv.index = 0;
|
||||
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv);
|
||||
|
||||
|
||||
if (signGroups.length > 0) {
|
||||
// signpost display control
|
||||
//
|
||||
|
||||
var signControl = document.createElement("DIV");
|
||||
signControl.id = "signControl"; // let's let a style sheet do most of the styling here
|
||||
|
||||
var controlBorder = document.createElement("DIV");
|
||||
controlBorder.id="top";
|
||||
signControl.appendChild(controlBorder);
|
||||
|
||||
var controlText = document.createElement("DIV");
|
||||
|
||||
controlBorder.appendChild(controlText);
|
||||
|
||||
controlText.innerHTML = "Signposts";
|
||||
|
||||
var dropdownDiv = document.createElement("DIV");
|
||||
|
||||
|
||||
$(controlText).click(function() {
|
||||
$(dropdownDiv).toggle();
|
||||
|
||||
});
|
||||
|
||||
|
||||
dropdownDiv.id="dropDown";
|
||||
signControl.appendChild(dropdownDiv);
|
||||
dropdownDiv.innerHTML="";
|
||||
|
||||
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(signControl);
|
||||
|
||||
|
||||
|
||||
var hasSignGroup = false;
|
||||
for (idx in signGroups) {
|
||||
var item = signGroups[idx];
|
||||
//console.log(item);
|
||||
label = item.label;
|
||||
hasSignGroup = true;
|
||||
var d = document.createElement("div");
|
||||
var n = document.createElement("input");
|
||||
n.type="checkbox";
|
||||
|
||||
$(n).data("label",label);
|
||||
jQuery(n).click(function(e) {
|
||||
var t = $(e.target);
|
||||
jQuery.each(markerCollection[t.data("label")], function(i,elem) {elem.setVisible(e.target.checked);});
|
||||
});
|
||||
|
||||
|
||||
if (item.checked) {
|
||||
n.checked = true;
|
||||
jQuery.each(markerCollection[label], function(i,elem) {elem.setVisible(n.checked);});
|
||||
}
|
||||
dropdownDiv.appendChild(d);
|
||||
d.appendChild(n)
|
||||
var textNode = document.createElement("text");
|
||||
textNode.innerHTML = label + "<br/>";
|
||||
d.appendChild(textNode);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function initRegions() {
|
||||
if (regionsInit) { return; }
|
||||
|
||||
regionsInit = true;
|
||||
|
||||
for (i in regionData) {
|
||||
var region = regionData[i];
|
||||
var converted = new google.maps.MVCArray();
|
||||
for (j in region.path) {
|
||||
var point = region.path[j];
|
||||
converted.push(fromWorldToLatLng(point.x, point.y, point.z));
|
||||
}
|
||||
|
||||
if (region.closed) {
|
||||
new google.maps.Polygon({clickable: false,
|
||||
geodesic: false,
|
||||
map: map,
|
||||
strokeColor: region.color,
|
||||
strokeOpacity: region.opacity,
|
||||
strokeWeight: 2,
|
||||
fillColor: region.color,
|
||||
fillOpacity: region.opacity * 0.25,
|
||||
zIndex: i,
|
||||
paths: converted
|
||||
});
|
||||
} else {
|
||||
new google.maps.Polyline({clickable: false,
|
||||
geodesic: false,
|
||||
map: map,
|
||||
strokeColor: region.color,
|
||||
strokeOpacity: region.opacity,
|
||||
strokeWeight: 2,
|
||||
zIndex: i,
|
||||
path: converted
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function initMarkers() {
|
||||
if (markersInit) { return; }
|
||||
|
||||
markersInit = true;
|
||||
|
||||
// first, give all collections an empty array to work with
|
||||
for (i in signGroups) {
|
||||
markerCollection[signGroups[i].label] = [];
|
||||
}
|
||||
|
||||
for (i in markerData) {
|
||||
var item = markerData[i];
|
||||
|
||||
// a default:
|
||||
var iconURL = '';
|
||||
if (item.type == 'spawn') {
|
||||
// don't filter spawn, always display
|
||||
|
||||
iconURL = 'http://google-maps-icons.googlecode.com/files/home.png';
|
||||
var converted = fromWorldToLatLng(item.x, item.y, item.z);
|
||||
var marker = new google.maps.Marker({position: converted,
|
||||
map: map,
|
||||
title: jQuery.trim(item.msg),
|
||||
icon: iconURL
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var matched = false;
|
||||
for (idx in signGroups) {
|
||||
var signGroup = signGroups[idx];
|
||||
var testfunc = signGroup.match;
|
||||
var label = signGroup.label;
|
||||
|
||||
if (testfunc(item)) {
|
||||
matched = true;
|
||||
|
||||
if (item.type == 'sign') { iconURL = 'signpost_icon.png';}
|
||||
|
||||
//console.log(signGroup.icon);
|
||||
if (signGroup.icon) { iconURL = signGroup.icon;
|
||||
}
|
||||
|
||||
var converted = fromWorldToLatLng(item.x, item.y, item.z);
|
||||
var marker = new google.maps.Marker({position: converted,
|
||||
map: map,
|
||||
title: jQuery.trim(item.msg),
|
||||
icon: iconURL,
|
||||
visible: false
|
||||
});
|
||||
|
||||
markerCollection[label].push(marker);
|
||||
|
||||
if (item.type == 'sign') {
|
||||
prepareSignMarker(marker, item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
// is this signpost doesn't match any of the groups in config.js, add it automatically to the "__others__" group
|
||||
if (item.type == 'sign') { iconURL = 'signpost_icon.png';}
|
||||
|
||||
var converted = fromWorldToLatLng(item.x, item.y, item.z);
|
||||
var marker = new google.maps.Marker({position: converted,
|
||||
map: map,
|
||||
title: jQuery.trim(item.msg),
|
||||
icon: iconURL,
|
||||
visible: false
|
||||
});
|
||||
if (markerCollection["__others__"]) {
|
||||
markerCollection["__others__"].push(marker);
|
||||
} else {
|
||||
markerCollection["__others__"] = [marker];
|
||||
}
|
||||
|
||||
if (item.type == 'sign') {
|
||||
prepareSignMarker(marker, item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function makeLink() {
|
||||
var a=location.href.substring(0,location.href.lastIndexOf(location.search))
|
||||
+ "?lat=" + map.getCenter().lat().toFixed(6)
|
||||
+ "&lng=" + map.getCenter().lng().toFixed(6)
|
||||
+ "&zoom=" + map.getZoom();
|
||||
document.getElementById("link").innerHTML = a;
|
||||
}
|
||||
|
||||
function initialize() {
|
||||
|
||||
var query = location.search.substring(1);
|
||||
|
||||
var defaultCenter = fromWorldToLatLng(config.center[0],
|
||||
config.center[1],
|
||||
config.center[2]);
|
||||
var lat = defaultCenter.lat();
|
||||
var lng = defaultCenter.lng();
|
||||
|
||||
var zoom = config.defaultZoom;
|
||||
var pairs = query.split("&");
|
||||
for (var i=0; i<pairs.length; i++) {
|
||||
// break each pair at the first "=" to obtain the argname and value
|
||||
var pos = pairs[i].indexOf("=");
|
||||
var argname = pairs[i].substring(0,pos).toLowerCase();
|
||||
var value = pairs[i].substring(pos+1).toLowerCase();
|
||||
|
||||
// process each possible argname
|
||||
if (argname == "lat") {lat = parseFloat(value);}
|
||||
if (argname == "lng") {lng = parseFloat(value);}
|
||||
if (argname == "zoom") {zoom = parseInt(value);}
|
||||
}
|
||||
|
||||
var mapTyepControlToggle = false
|
||||
if (mapTypeIds.length > 1) {
|
||||
mapTyepControlToggle = true
|
||||
}
|
||||
var mapOptions = {
|
||||
zoom: zoom,
|
||||
center: new google.maps.LatLng(lat, lng),
|
||||
navigationControl: true,
|
||||
scaleControl: false,
|
||||
mapTypeControl: mapTyepControlToggle,
|
||||
mapTypeControlOptions: {
|
||||
mapTypeIds: mapTypeIds
|
||||
},
|
||||
mapTypeId: mapTypeIdDefault,
|
||||
streetViewControl: false,
|
||||
backgroundColor: config.bg_color,
|
||||
};
|
||||
map = new google.maps.Map(document.getElementById('mcmap'), mapOptions);
|
||||
|
||||
if(config.debug) {
|
||||
map.overlayMapTypes.insertAt(0, new CoordMapType(new google.maps.Size(config.tileSize, config.tileSize)));
|
||||
|
||||
google.maps.event.addListener(map, 'click', function(event) {
|
||||
//console.log("latLng; " + event.latLng.lat() + ", " + event.latLng.lng());
|
||||
|
||||
var pnt = map.getProjection().fromLatLngToPoint(event.latLng);
|
||||
//console.log("point: " + pnt);
|
||||
|
||||
var pxx = pnt.x * config.tileSize * Math.pow(2, config.maxZoom);
|
||||
var pxy = pnt.y * config.tileSize * Math.pow(2, config.maxZoom);
|
||||
//console.log("pixel: " + pxx + ", " + pxy);
|
||||
});
|
||||
}
|
||||
|
||||
// Now attach the coordinate map type to the map's registry
|
||||
for (idx in MCMapType) {
|
||||
map.mapTypes.set('mcmap' + MCMapType[idx].name, MCMapType[idx]);
|
||||
}
|
||||
|
||||
// We can now set the map to use the 'coordinate' map type
|
||||
map.setMapTypeId(mapTypeIdDefault);
|
||||
|
||||
// initialize the markers and regions
|
||||
initMarkers();
|
||||
initRegions();
|
||||
drawMapControls();
|
||||
|
||||
//makeLink();
|
||||
|
||||
// Make the link again whenever the map changes
|
||||
google.maps.event.addListener(map, 'zoom_changed', function() {
|
||||
makeLink();
|
||||
});
|
||||
google.maps.event.addListener(map, 'center_changed', function() {
|
||||
makeLink();
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
// our custom projection maps Latitude to Y, and Longitude to X as normal,
|
||||
// but it maps the range [0.0, 1.0] to [0, tileSize] in both directions
|
||||
// so it is easier to position markers, etc. based on their position
|
||||
// (find their position in the lowest-zoom image, and divide by tileSize)
|
||||
function MCMapProjection() {
|
||||
this.inverseTileSize = 1.0 / config.tileSize;
|
||||
}
|
||||
|
||||
MCMapProjection.prototype.fromLatLngToPoint = function(latLng) {
|
||||
var x = latLng.lng() * config.tileSize;
|
||||
var y = latLng.lat() * config.tileSize;
|
||||
return new google.maps.Point(x, y);
|
||||
};
|
||||
|
||||
MCMapProjection.prototype.fromPointToLatLng = function(point) {
|
||||
var lng = point.x * this.inverseTileSize;
|
||||
var lat = point.y * this.inverseTileSize;
|
||||
return new google.maps.LatLng(lat, lng);
|
||||
};
|
||||
|
||||
// helper to get map LatLng from world coordinates
|
||||
// takes arguments in X, Y, Z order
|
||||
// (arguments are *out of order*, because within the function we use
|
||||
// the axes like the rest of Minecraft Overviewer -- with the Z and Y
|
||||
// flipped from normal minecraft usage.)
|
||||
function fromWorldToLatLng(x, z, y)
|
||||
{
|
||||
// the width and height of all the highest-zoom tiles combined, inverted
|
||||
var perPixel = 1.0 / (config.tileSize * Math.pow(2, config.maxZoom));
|
||||
|
||||
// This information about where the center column is may change with a different
|
||||
// drawing implementation -- check it again after any drawing overhauls!
|
||||
|
||||
// point (0, 0, 127) is at (0.5, 0.0) of tile (tiles/2 - 1, tiles/2)
|
||||
// so the Y coordinate is at 0.5, and the X is at 0.5 - ((tileSize / 2) / (tileSize * 2^maxZoom))
|
||||
// or equivalently, 0.5 - (1 / 2^(maxZoom + 1))
|
||||
var lng = 0.5 - (1.0 / Math.pow(2, config.maxZoom + 1));
|
||||
var lat = 0.5;
|
||||
|
||||
// the following metrics mimic those in ChunkRenderer.chunk_render in "chunk.py"
|
||||
// or, equivalently, chunk_render in src/iterate.c
|
||||
|
||||
// each block on X axis adds 12px to x and subtracts 6px from y
|
||||
lng += 12 * x * perPixel;
|
||||
lat -= 6 * x * perPixel;
|
||||
|
||||
// each block on Y axis adds 12px to x and adds 6px to y
|
||||
lng += 12 * y * perPixel;
|
||||
lat += 6 * y * perPixel;
|
||||
|
||||
// each block down along Z adds 12px to y
|
||||
lat += 12 * (128 - z) * perPixel;
|
||||
|
||||
// add on 12 px to the X coordinate to center our point
|
||||
lng += 12 * perPixel;
|
||||
|
||||
return new google.maps.LatLng(lat, lng);
|
||||
}
|
||||
|
||||
function getTileUrlGenerator(path, path_base) {
|
||||
return function(tile, zoom) {
|
||||
var url = path;
|
||||
var url_base = ( path_base ? path_base : '' );
|
||||
if(tile.x < 0 || tile.x >= Math.pow(2, zoom) || tile.y < 0 || tile.y >= Math.pow(2, zoom)) {
|
||||
url += '/blank';
|
||||
} else if(zoom == 0) {
|
||||
url += '/base';
|
||||
} else {
|
||||
for(var z = zoom - 1; z >= 0; --z) {
|
||||
var x = Math.floor(tile.x / Math.pow(2, z)) % 2;
|
||||
var y = Math.floor(tile.y / Math.pow(2, z)) % 2;
|
||||
url += '/' + (x + 2 * y);
|
||||
}
|
||||
}
|
||||
url = url + '.' + config.fileExt;
|
||||
if(config.cacheMinutes > 0) {
|
||||
var d = new Date();
|
||||
url += '?c=' + Math.floor(d.getTime() / (1000 * 60 * config.cacheMinutes));
|
||||
}
|
||||
return(url_base + url);
|
||||
}
|
||||
}
|
||||
|
||||
var MCMapOptions = new Array;
|
||||
var MCMapType = new Array;
|
||||
var mapTypeIdDefault = null;
|
||||
var mapTypeIds = [];
|
||||
for (idx in mapTypeData) {
|
||||
var view = mapTypeData[idx];
|
||||
|
||||
MCMapOptions[view.label] = {
|
||||
getTileUrl: getTileUrlGenerator(view.path, view.base),
|
||||
tileSize: new google.maps.Size(config.tileSize, config.tileSize),
|
||||
maxZoom: config.maxZoom,
|
||||
minZoom: 0,
|
||||
isPng: !(config.fileExt.match(/^png$/i) == null)
|
||||
};
|
||||
|
||||
MCMapType[view.label] = new google.maps.ImageMapType(MCMapOptions[view.label]);
|
||||
MCMapType[view.label].name = view.label;
|
||||
MCMapType[view.label].alt = "Minecraft " + view.label + " Map";
|
||||
MCMapType[view.label].projection = new MCMapProjection();
|
||||
if (mapTypeIdDefault == null) {
|
||||
mapTypeIdDefault = 'mcmap' + view.label;
|
||||
}
|
||||
mapTypeIds.push('mcmap' + view.label);
|
||||
}
|
||||
|
||||
function CoordMapType() {
|
||||
}
|
||||
|
||||
function CoordMapType(tileSize) {
|
||||
this.tileSize = tileSize;
|
||||
}
|
||||
|
||||
CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) {
|
||||
var div = ownerDocument.createElement('DIV');
|
||||
div.innerHTML = "(" + coord.x + ", " + coord.y + ", " + zoom + ")";
|
||||
div.innerHTML += "<br />";
|
||||
div.innerHTML += MCMapOptions.getTileUrl(coord, zoom);
|
||||
div.style.width = this.tileSize.width + 'px';
|
||||
div.style.height = this.tileSize.height + 'px';
|
||||
div.style.fontSize = '10';
|
||||
div.style.borderStyle = 'solid';
|
||||
div.style.borderWidth = '1px';
|
||||
div.style.borderColor = '#AAAAAA';
|
||||
return div;
|
||||
};
|
||||
@@ -2,18 +2,16 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
|
||||
<link rel="stylesheet" href="style.css" type="text/css" />
|
||||
<script type="text/javascript" src="config.js"></script>
|
||||
<script type="text/javascript"
|
||||
src="http://maps.google.com/maps/api/js?sensor=false">
|
||||
</script>
|
||||
<link rel="stylesheet" href="overviewer.css" type="text/css" />
|
||||
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
|
||||
<script type="text/javascript" src="functions.js"></script>
|
||||
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
|
||||
<script type="text/javascript" src="overviewerConfig.js"></script>
|
||||
<script type="text/javascript" src="overviewer.js"></script>
|
||||
<script type="text/javascript" src="markers.js"></script>
|
||||
<script type="text/javascript" src="regions.js"></script>
|
||||
</head>
|
||||
<!-- Generated at: {time} -->
|
||||
<body onload="initialize()">
|
||||
<div id="mcmap" style="width:100%; height:100%"></div>
|
||||
<body onload="overviewer.util.initialize()">
|
||||
<div id="mcmap"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
html { height: 100% }
|
||||
body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; }
|
||||
#mcmap { height: 100% }
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
background-color: #000;
|
||||
}
|
||||
|
||||
#mcmap {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.infoWindow {
|
||||
height: 100px;
|
||||
@@ -17,13 +29,13 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; }
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#signControl {
|
||||
#customControl {
|
||||
padding: 5px;
|
||||
height: 15px;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
#signControl > div#top {
|
||||
#customControl > div#top {
|
||||
background-color: #fff;
|
||||
border: 2px solid #000;
|
||||
text-align: center;
|
||||
@@ -33,7 +45,14 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; }
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#signControl > div#dropDown {
|
||||
#customControl > div#dropDown {
|
||||
border: 1px solid #000;
|
||||
font-size: 12px;
|
||||
background-color: #fff;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#customControl > div#button {
|
||||
border: 1px solid #000;
|
||||
font-size: 12px;
|
||||
background-color: #fff;
|
||||
903
overviewer_core/data/web_assets/overviewer.js
Normal file
903
overviewer_core/data/web_assets/overviewer.js
Normal file
@@ -0,0 +1,903 @@
|
||||
var overviewer = {
|
||||
/**
|
||||
* This holds the map, probably the most important var in this file
|
||||
*/
|
||||
'map': null,
|
||||
/**
|
||||
* These are collections of data used in various places
|
||||
*/
|
||||
'collections': {
|
||||
/**
|
||||
* A list of lists of raw marker data objects, this will allow for an
|
||||
* arbitrary number of marker data sources. This replaces the old
|
||||
* markerData var from markers.js. Now you can add markers by including
|
||||
* a file with:
|
||||
* overviewer.collections.markerDatas.push([<your list of markers>]);
|
||||
*/
|
||||
'markerDatas': [],
|
||||
/**
|
||||
* The actual Marker objects are stored here.
|
||||
*/
|
||||
'markers': {},
|
||||
/**
|
||||
* Same as markerDatas, list of lists of raw region objects.
|
||||
*/
|
||||
'regionDatas': [],
|
||||
/**
|
||||
* The actual Region objects.
|
||||
*/
|
||||
'regions': {},
|
||||
/**
|
||||
* Overlay mapTypes (like Spawn) will go in here.
|
||||
*/
|
||||
'overlays': [],
|
||||
/**
|
||||
* MapTypes that aren't overlays will end up in here.
|
||||
*/
|
||||
'mapTypes': {},
|
||||
/**
|
||||
* The mapType names are in here.
|
||||
*/
|
||||
'mapTypeIds': [],
|
||||
/**
|
||||
* This is the current infoWindow object, we keep track of it so that
|
||||
* there is only one open at a time.
|
||||
*/
|
||||
'infoWindow': null
|
||||
},
|
||||
'util': {
|
||||
/**
|
||||
* General initialization function, called when the page is loaded.
|
||||
* Probably shouldn't need changing unless some very different kind of new
|
||||
* feature gets added.
|
||||
*/
|
||||
'initialize': function() {
|
||||
overviewer.util.initializeClassPrototypes();
|
||||
overviewer.util.initializeMapTypes();
|
||||
overviewer.util.initializeMap();
|
||||
overviewer.util.initializeMarkers();
|
||||
overviewer.util.initializeRegions();
|
||||
overviewer.util.createMapControls();
|
||||
},
|
||||
/**
|
||||
* This adds some methods to these classes because Javascript is stupid
|
||||
* and this seems like the best way to avoid re-creating the same methods
|
||||
* on each object at object creation time.
|
||||
*/
|
||||
'initializeClassPrototypes': function() {
|
||||
overviewer.classes.MapProjection.prototype.fromLatLngToPoint = function(latLng) {
|
||||
var x = latLng.lng() * overviewerConfig.CONST.tileSize;
|
||||
var y = latLng.lat() * overviewerConfig.CONST.tileSize;
|
||||
return new google.maps.Point(x, y);
|
||||
};
|
||||
|
||||
overviewer.classes.MapProjection.prototype.fromPointToLatLng = function(point) {
|
||||
var lng = point.x * this.inverseTileSize;
|
||||
var lat = point.y * this.inverseTileSize;
|
||||
return new google.maps.LatLng(lat, lng);
|
||||
};
|
||||
|
||||
overviewer.classes.CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) {
|
||||
var div = ownerDocument.createElement('DIV');
|
||||
div.innerHTML = '(' + coord.x + ', ' + coord.y + ', ' + zoom +
|
||||
')' + '<br />';
|
||||
//TODO: figure out how to get the current mapType, I think this
|
||||
//will add the maptile url to the grid thing once it works
|
||||
|
||||
//div.innerHTML += overviewer.collections.mapTypes[0].getTileUrl(coord, zoom);
|
||||
|
||||
//this should probably just have a css class
|
||||
div.style.width = this.tileSize.width + 'px';
|
||||
div.style.height = this.tileSize.height + 'px';
|
||||
div.style.fontSize = '10px';
|
||||
div.style.borderStyle = 'solid';
|
||||
div.style.borderWidth = '1px';
|
||||
div.style.borderColor = '#AAAAAA';
|
||||
return div;
|
||||
};
|
||||
},
|
||||
/**
|
||||
* Setup the varous mapTypes before we actually create the map. This used
|
||||
* to be a bunch of crap down at the bottom of functions.js
|
||||
*/
|
||||
'initializeMapTypes': function() {
|
||||
var mapOptions = {};
|
||||
for (i in overviewerConfig.mapTypes) {
|
||||
var view = overviewerConfig.mapTypes[i];
|
||||
var imageFormat = view.imgformat ? view.imgformat : 'png';
|
||||
mapOptions[view.label] = {
|
||||
'getTileUrl': overviewer.gmap.getTileUrlGenerator(view.path,
|
||||
view.base, imageFormat),
|
||||
'tileSize': new google.maps.Size(
|
||||
overviewerConfig.CONST.tileSize,
|
||||
overviewerConfig.CONST.tileSize),
|
||||
'maxZoom': overviewerConfig.map.maxZoom,
|
||||
'minZoom': overviewerConfig.map.minZoom,
|
||||
'isPng': imageFormat.toLowerCase() == 'png'
|
||||
}
|
||||
overviewer.collections.mapTypes[view.label] = new google.maps.ImageMapType(
|
||||
mapOptions[view.label]);
|
||||
overviewer.collections.mapTypes[view.label].name = view.label;
|
||||
overviewer.collections.mapTypes[view.label].alt = 'Minecraft ' +
|
||||
view.label + ' Map';
|
||||
overviewer.collections.mapTypes[view.label].projection =
|
||||
new overviewer.classes.MapProjection();
|
||||
if (view.overlay) {
|
||||
overviewer.collections.overlays.push(
|
||||
overviewer.collections.mapTypes[view.label]);
|
||||
} else {
|
||||
overviewer.collections.mapTypeIds.push(
|
||||
overviewerConfig.CONST.mapDivId + view.label);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* This is where the magic happens. We setup the map with all it's
|
||||
* options. The query string is also parsed here so we can know if
|
||||
* we should be looking at a particular point on the map or just use
|
||||
* the default view.
|
||||
*/
|
||||
'initializeMap': function() {
|
||||
var defaultCenter = overviewer.util.fromWorldToLatLng(
|
||||
overviewerConfig.map.center[0], overviewerConfig.map.center[1],
|
||||
overviewerConfig.map.center[2]);
|
||||
var lat = defaultCenter.lat();
|
||||
var lng = defaultCenter.lng();
|
||||
var zoom = overviewerConfig.map.defaultZoom;
|
||||
var mapcenter;
|
||||
queryParams = overviewer.util.parseQueryString();
|
||||
if (queryParams.lat) {
|
||||
lat = parseFloat(queryParams.lat);
|
||||
}
|
||||
if (queryParams.lng) {
|
||||
lng = parseFloat(queryParams.lng);
|
||||
}
|
||||
if (queryParams.zoom) {
|
||||
if (queryParams.zoom == 'max') {
|
||||
zoom = overviewerConfig.map.maxZoom;
|
||||
} else if (queryParams.zoom == 'min') {
|
||||
zoom = overviewerConfig.map.minZoom;
|
||||
} else {
|
||||
zoom = parseInt(queryParams.zoom);
|
||||
if (zoom < 0 && zoom + overviewerConfig.map.maxZoom >= 0) {
|
||||
//if zoom is negative, try to treat as "zoom out from max zoom"
|
||||
zoom += overviewerConfig.map.maxZoom;
|
||||
} else {
|
||||
//fall back to default zoom
|
||||
zoom = overviewerConfig.map.defaultZoom;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (queryParams.x && queryParams.y && queryParams.z) {
|
||||
mapcenter = overviewer.util.fromWorldToLatLng(queryParams.x,
|
||||
queryParams.y, queryParams.z);
|
||||
// Add a market indicating the user-supplied position
|
||||
overviewer.collections.markerDatas.push([{
|
||||
'msg': 'Coordinates ' + queryParams.x + ', ' +
|
||||
queryParams.y + ', ' + queryParams.z,
|
||||
'y': parseFloat(queryParams.y),
|
||||
'x': parseFloat(queryParams.x),
|
||||
'z': parseFloat(queryParams.z),
|
||||
'type': 'querypos'}]);
|
||||
} else {
|
||||
mapcenter = new google.maps.LatLng(lat, lng);
|
||||
}
|
||||
var mapOptions = {
|
||||
zoom: zoom,
|
||||
center: mapcenter,
|
||||
panControl: overviewerConfig.map.controls.pan,
|
||||
scaleControl: false,
|
||||
mapTypeControl: overviewerConfig.map.controls.mapType &&
|
||||
overviewer.collections.mapTypeIds.length > 1,
|
||||
mapTypeControlOptions: {
|
||||
mapTypeIds: overviewer.collections.mapTypeIds
|
||||
},
|
||||
mapTypeId: overviewer.util.getDefaultMapTypeId(),
|
||||
streetViewControl: false,
|
||||
zoomControl: overviewerConfig.map.controls.zoom,
|
||||
backgroundColor: overviewer.util.getMapTypeBackgroundColor(
|
||||
overviewer.util.getDefaultMapTypeId())
|
||||
};
|
||||
overviewer.map = new google.maps.Map(document.getElementById(
|
||||
overviewerConfig.CONST.mapDivId), mapOptions);
|
||||
|
||||
if (overviewerConfig.map.debug) {
|
||||
overviewer.map.overlayMapTypes.insertAt(0,
|
||||
new overviewer.classes.CoordMapType(new google.maps.Size(
|
||||
overviewerConfig.CONST.tileSize,
|
||||
overviewerConfig.CONST.tileSize)));
|
||||
google.maps.event.addListener(overviewer.map, 'click', function(event) {
|
||||
overviewer.util.debug('latLng: (' + event.latLng.lat() +
|
||||
', ' + event.latLng.lng() + ')');
|
||||
var pnt = overviewer.map.getProjection().fromLatLngToPoint(event.latLng);
|
||||
overviewer.util.debug('point: ' + pnt);
|
||||
var pxx = pnt.x * overviewerConfig.CONST.tileSize *
|
||||
Math.pow(2, overviewerConfig.map.maxZoom);
|
||||
var pxy = pnt.y * overviewerConfig.CONST.tileSize *
|
||||
Math.pow(2, overviewerConfig.map.maxZoom);
|
||||
overviewer.util.debug('pixel: (' + pxx + ', ' + pxy + ')');
|
||||
});
|
||||
}
|
||||
|
||||
// Now attach the coordinate map type to the map's registry
|
||||
for (i in overviewer.collections.mapTypes) {
|
||||
overviewer.map.mapTypes.set(overviewerConfig.CONST.mapDivId +
|
||||
overviewer.collections.mapTypes[i].name,
|
||||
overviewer.collections.mapTypes[i]);
|
||||
}
|
||||
|
||||
// Make the link again whenever the map changes
|
||||
google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function() {
|
||||
$('#'+overviewerConfig.CONST.mapDivId).css(
|
||||
'background-color', overviewer.util.getMapTypeBackgroundColor(
|
||||
overviewer.map.getMapTypeId()));
|
||||
});
|
||||
// We can now set the map to use the 'coordinate' map type
|
||||
overviewer.map.setMapTypeId(overviewer.util.getDefaultMapTypeId());
|
||||
},
|
||||
/**
|
||||
* Read through overviewer.collections.markerDatas and create Marker
|
||||
* objects and stick them in overviewer.collections.markers . This
|
||||
* should probably be done differently at some point so that we can
|
||||
* support markers that change position more easily.
|
||||
*/
|
||||
'initializeMarkers': function() {
|
||||
//first, give all collections an empty array to work with
|
||||
for (i in overviewerConfig.objectGroups.signs) {
|
||||
overviewer.util.debug('Found sign group: ' +
|
||||
overviewerConfig.objectGroups.signs[i].label);
|
||||
overviewer.collections.markers[
|
||||
overviewerConfig.objectGroups.signs[i].label] = [];
|
||||
}
|
||||
for (i in overviewer.collections.markerDatas) {
|
||||
var markerData = overviewer.collections.markerDatas[i];
|
||||
for (j in markerData) {
|
||||
var item = markerData[j];
|
||||
// a default:
|
||||
var iconURL = '';
|
||||
if (item.type == 'spawn') {
|
||||
// don't filter spawn, always display
|
||||
var marker = new google.maps.Marker({
|
||||
'position': overviewer.util.fromWorldToLatLng(item.x,
|
||||
item.y, item.z),
|
||||
'map': overviewer.map,
|
||||
'title': jQuery.trim(item.msg),
|
||||
'icon': overviewerConfig.CONST.image.spawnMarker
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (item.type == 'querypos') {
|
||||
// Set on page load if MC x/y/z coords are given in the
|
||||
// query string
|
||||
var marker = new google.maps.Marker({
|
||||
'position': overviewer.util.fromWorldToLatLng(item.x,
|
||||
item.y, item.z),
|
||||
'map': overviewer.map,
|
||||
'title': jQuery.trim(item.msg),
|
||||
'icon': overviewerConfig.CONST.image.queryMarker
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
var matched = false;
|
||||
for (j in overviewerConfig.objectGroups.signs) {
|
||||
var signGroup = overviewerConfig.objectGroups.signs[j];
|
||||
var label = signGroup.label;
|
||||
if (signGroup.match(item)) {
|
||||
matched = true;
|
||||
// can add custom types of images for externally defined
|
||||
// item types, like 'command' here.
|
||||
if (item.type == 'sign') {
|
||||
iconURL = overviewerConfig.CONST.image.signMarker;
|
||||
}
|
||||
overviewer.util.debug('Sign icon: ' + signGroup.icon);
|
||||
if (signGroup.icon) {
|
||||
iconURL = signGroup.icon;
|
||||
}
|
||||
var marker = new google.maps.Marker({
|
||||
'position': overviewer.util.fromWorldToLatLng(item.x,
|
||||
item.y, item.z),
|
||||
'map': overviewer.map,
|
||||
'title': jQuery.trim(item.msg),
|
||||
'icon': iconURL,
|
||||
'visible': false
|
||||
});
|
||||
overviewer.util.debug(label);
|
||||
overviewer.collections.markers[label].push(marker);
|
||||
if (item.type == 'sign') {
|
||||
overviewer.util.createMarkerInfoWindow(marker);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!matched) {
|
||||
// is this signpost doesn't match any of the groups in
|
||||
// config.js, add it automatically to the "__others__" group
|
||||
if (item.type == 'sign') {
|
||||
iconURL = overviewerConfig.CONST.image.signMarker;
|
||||
}
|
||||
var marker = new google.maps.Marker({
|
||||
'position': overviewer.util.fromWorldToLatLng(item.x,
|
||||
item.y, item.z),
|
||||
'map': overviewer.map,
|
||||
'title': jQuery.trim(item.msg),
|
||||
'icon': iconURL,
|
||||
'visible': false
|
||||
});
|
||||
if (overviewer.collections.markers['__others__']) {
|
||||
overviewer.collections.markers['__others__'].push(marker);
|
||||
} else {
|
||||
overviewer.collections.markers['__others__'] = [marker];
|
||||
}
|
||||
if (item.type == 'sign') {
|
||||
overviewer.util.createMarkerInfoWindow(marker, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Same as initializeMarkers() for the most part.
|
||||
*/
|
||||
'initializeRegions': function() {
|
||||
for (i in overviewerConfig.objectGroups.regions) {
|
||||
overviewer.collections.regions[overviewerConfig.objectGroups.regions[i].label] = [];
|
||||
}
|
||||
for (i in overviewer.collections.regionDatas) {
|
||||
var regionData = overviewer.collections.regionDatas[i];
|
||||
for (j in regionData) {
|
||||
var region = regionData[j];
|
||||
// pull all the points out of the regions file.
|
||||
var converted = new google.maps.MVCArray();
|
||||
for (k in region.path) {
|
||||
var point = region.path[k];
|
||||
converted.push(overviewer.util.fromWorldToLatLng(
|
||||
point.x, point.y, point.z));
|
||||
|
||||
}
|
||||
for (k in overviewerConfig.objectGroups.regions) {
|
||||
var regionGroup = overviewerConfig.objectGroups.regions[k];
|
||||
var clickable = regionGroup.clickable;
|
||||
var label = regionGroup.label;
|
||||
|
||||
if(region.label) {
|
||||
var name = region.label
|
||||
} else {
|
||||
var name = 'rawr';
|
||||
clickable = false; // if it doesn't have a name, we dont have to show it.
|
||||
}
|
||||
|
||||
if(region.opacity) {
|
||||
var strokeOpacity = region.opacity;
|
||||
var fillOpacity = region.opacity * 0.25;
|
||||
} else {
|
||||
var strokeOpacity = region.strokeOpacity;
|
||||
var fillOpacity = region.fillOpacity;
|
||||
}
|
||||
|
||||
if (region.closed) {
|
||||
var shape = new google.maps.Polygon({
|
||||
'name': name,
|
||||
'clickable': clickable,
|
||||
'geodesic': false,
|
||||
'map': null,
|
||||
'strokeColor': region.color,
|
||||
'strokeOpacity': strokeOpacity,
|
||||
'strokeWeight': overviewerConfig.CONST.regionStrokeWeight,
|
||||
'fillColor': region.color,
|
||||
'fillOpacity': fillOpacity,
|
||||
'zIndex': j,
|
||||
'paths': converted
|
||||
});
|
||||
} else {
|
||||
var shape = new google.maps.Polyline({
|
||||
'name': name,
|
||||
'clickable': clickable,
|
||||
'geodesic': false,
|
||||
'map': null,
|
||||
'strokeColor': region.color,
|
||||
'strokeOpacity': strokeOpacity,
|
||||
'strokeWeight': overviewerConfig.CONST.regionStrokeWeight,
|
||||
'zIndex': j,
|
||||
'path': converted
|
||||
});
|
||||
}
|
||||
overviewer.collections.regions[label].push(shape);
|
||||
|
||||
if (clickable) {
|
||||
overviewer.util.createRegionInfoWindow(shape);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Change the map's div's background color according to the mapType's bg_color setting
|
||||
*
|
||||
* @param string mapTypeId
|
||||
* @return string
|
||||
*/
|
||||
'getMapTypeBackgroundColor': function(mapTypeId) {
|
||||
for(i in overviewerConfig.mapTypes) {
|
||||
if( overviewerConfig.CONST.mapDivId +
|
||||
overviewerConfig.mapTypes[i].label == mapTypeId ) {
|
||||
overviewer.util.debug('Found background color for: ' +
|
||||
overviewerConfig.mapTypes[i].bg_color);
|
||||
return overviewerConfig.mapTypes[i].bg_color;
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Gee, I wonder what this does.
|
||||
*
|
||||
* @param string msg
|
||||
*/
|
||||
'debug': function(msg) {
|
||||
if (overviewerConfig.map.debug) {
|
||||
console.log(msg);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Simple helper function to split the query string into key/value
|
||||
* pairs. Doesn't do any type conversion but both are lowercase'd.
|
||||
*
|
||||
* @return Object
|
||||
*/
|
||||
'parseQueryString': function() {
|
||||
var results = {};
|
||||
var queryString = location.search.substring(1);
|
||||
var pairs = queryString.split('&');
|
||||
for (i in pairs) {
|
||||
var pos = pairs[i].indexOf('=');
|
||||
var key = pairs[i].substring(0,pos).toLowerCase();
|
||||
var value = pairs[i].substring(pos+1).toLowerCase();
|
||||
overviewer.util.debug( 'Found GET paramter: ' + key + ' = ' + value);
|
||||
results[key] = value;
|
||||
}
|
||||
return results;
|
||||
},
|
||||
/**
|
||||
* Set the link (at the bottom of the screen) to the current view.
|
||||
* TODO: make this preserve the mapTypeId as well
|
||||
*/
|
||||
'setViewUrl': function() {
|
||||
var displayZoom = overviewer.map.getZoom();
|
||||
if (displayZoom == overviewerConfig.map.maxZoom) {
|
||||
displayZoom = 'max';
|
||||
} else {
|
||||
displayZoom -= overviewerConfig.map.maxZoom;
|
||||
}
|
||||
var point;
|
||||
var point = overviewer.util.fromLatLngToWorld(
|
||||
overviewer.map.getCenter().lat(), overviewer.map.getCenter().lng());
|
||||
var viewUrl = location.href.substring(0, location.href.lastIndexOf(
|
||||
location.search))
|
||||
+ '?x=' + Math.floor(point.x)
|
||||
+ '&y=' + Math.floor(point.y)
|
||||
+ '&z=' + Math.floor(point.z)
|
||||
+ '&zoom=' + displayZoom;
|
||||
document.getElementById('link').innerHTML = viewUrl;
|
||||
|
||||
},
|
||||
'getDefaultMapTypeId': function() {
|
||||
return overviewer.collections.mapTypeIds[0];
|
||||
},
|
||||
/**
|
||||
* helper to get map LatLng from world coordinates takes arguments in
|
||||
* X, Y, Z order (arguments are *out of order*, because within the
|
||||
* function we use the axes like the rest of Minecraft Overviewer --
|
||||
* with the Z and Y flipped from normal minecraft usage.)
|
||||
*
|
||||
* @param int x
|
||||
* @param int z
|
||||
* @param int y
|
||||
*
|
||||
* @return google.maps.LatLng
|
||||
*/
|
||||
'fromWorldToLatLng': function(x, z, y) {
|
||||
// the width and height of all the highest-zoom tiles combined,
|
||||
// inverted
|
||||
var perPixel = 1.0 / (overviewerConfig.CONST.tileSize *
|
||||
Math.pow(2, overviewerConfig.map.maxZoom));
|
||||
|
||||
// This information about where the center column is may change with
|
||||
// a different drawing implementation -- check it again after any
|
||||
// drawing overhauls!
|
||||
|
||||
// point (0, 0, 127) is at (0.5, 0.0) of tile (tiles/2 - 1, tiles/2)
|
||||
// so the Y coordinate is at 0.5, and the X is at 0.5 -
|
||||
// ((tileSize / 2) / (tileSize * 2^maxZoom))
|
||||
// or equivalently, 0.5 - (1 / 2^(maxZoom + 1))
|
||||
var lng = 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.maxZoom + 1));
|
||||
var lat = 0.5;
|
||||
|
||||
// the following metrics mimic those in ChunkRenderer.chunk_render
|
||||
// in "chunk.py" or, equivalently, chunk_render in src/iterate.c
|
||||
|
||||
// each block on X axis adds 12px to x and subtracts 6px from y
|
||||
lng += 12 * x * perPixel;
|
||||
lat -= 6 * x * perPixel;
|
||||
|
||||
// each block on Y axis adds 12px to x and adds 6px to y
|
||||
lng += 12 * y * perPixel;
|
||||
lat += 6 * y * perPixel;
|
||||
|
||||
// each block down along Z adds 12px to y
|
||||
lat += 12 * (128 - z) * perPixel;
|
||||
|
||||
// add on 12 px to the X coordinate to center our point
|
||||
lng += 12 * perPixel;
|
||||
|
||||
return new google.maps.LatLng(lat, lng);
|
||||
},
|
||||
/**
|
||||
* The opposite of fromWorldToLatLng
|
||||
* NOTE: X, Y and Z in this function are Minecraft world definitions
|
||||
* (that is, X is horizontal, Y is altitude and Z is vertical).
|
||||
*
|
||||
* @param float lat
|
||||
* @param float lng
|
||||
*
|
||||
* @return Array
|
||||
*/
|
||||
'fromLatLngToWorld': function(lat, lng) {
|
||||
// Initialize world x/y/z object to be returned
|
||||
var point = Array();
|
||||
point.x = 0;
|
||||
point.y = 64;
|
||||
point.z = 0;
|
||||
|
||||
// the width and height of all the highest-zoom tiles combined,
|
||||
// inverted
|
||||
var perPixel = 1.0 / (overviewerConfig.CONST.tileSize *
|
||||
Math.pow(2, overviewerConfig.map.maxZoom));
|
||||
|
||||
// Revert base positioning
|
||||
// See equivalent code in fromWorldToLatLng()
|
||||
lng -= 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.maxZoom + 1));
|
||||
lat -= 0.5;
|
||||
|
||||
// I'll admit, I plugged this into Wolfram Alpha:
|
||||
// a = (x * 12 * r) + (z * 12 * r), b = (z * 6 * r) - (x * 6 * r)
|
||||
// And I don't know the math behind solving for for X and Z given
|
||||
// A (lng) and B (lat). But Wolfram Alpha did. :) I'd welcome
|
||||
// suggestions for splitting this up into long form and documenting
|
||||
// it. -RF
|
||||
point.x = (lng - 2 * lat) / (24 * perPixel)
|
||||
point.z = (lng + 2 * lat) / (24 * perPixel)
|
||||
|
||||
// Adjust for the fact that we we can't figure out what Y is given
|
||||
// only latitude and longitude, so assume Y=64.
|
||||
point.x += 64 + 1;
|
||||
point.z -= 64 + 2;
|
||||
|
||||
return point;
|
||||
},
|
||||
/**
|
||||
* Create and draw the various map controls and other related things
|
||||
* like the compass, current view link, etc.
|
||||
*/
|
||||
'createMapControls': function() {
|
||||
// viewstate link (little link to where you're looking at the map,
|
||||
// normally bottom left)
|
||||
var viewStateDiv = document.createElement('DIV');
|
||||
viewStateDiv.id='link';
|
||||
// add it to the map, bottom left.
|
||||
if (overviewerConfig.map.controls.link) {
|
||||
google.maps.event.addListener(overviewer.map, 'zoom_changed', function() {
|
||||
overviewer.util.setViewUrl();
|
||||
});
|
||||
google.maps.event.addListener(overviewer.map, 'center_changed', function() {
|
||||
overviewer.util.setViewUrl();
|
||||
});
|
||||
overviewer.map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(viewStateDiv);
|
||||
}
|
||||
|
||||
// compass rose, in the top right corner
|
||||
var compassDiv = document.createElement('DIV');
|
||||
compassDiv.style.padding = '5px';
|
||||
var compassImg = document.createElement('IMG');
|
||||
compassImg.src = overviewerConfig.CONST.image.compass;
|
||||
compassDiv.appendChild(compassImg);
|
||||
compassDiv.index = 0;
|
||||
// add it to the map, top right.
|
||||
if (overviewerConfig.map.controls.compass) {
|
||||
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv);
|
||||
}
|
||||
|
||||
// Spawn button
|
||||
var homeControlDiv = document.createElement('DIV');
|
||||
var homeControl = new overviewer.classes.HomeControl(homeControlDiv);
|
||||
homeControlDiv.id = 'customControl';
|
||||
homeControlDiv.index = 1;
|
||||
if (overviewerConfig.map.controls.spawn) {
|
||||
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv);
|
||||
}
|
||||
|
||||
// only need to create the control if there are items in the list.
|
||||
// as defined in config.js
|
||||
if (overviewerConfig.objectGroups.signs.length > 0) {
|
||||
// signpost display control
|
||||
var items = [];
|
||||
for (i in overviewerConfig.objectGroups.signs) {
|
||||
var signGroup = overviewerConfig.objectGroups.signs[i];
|
||||
var iconURL = signGroup.icon;
|
||||
if(!iconURL) {
|
||||
iconURL = overviewerConfig.CONST.image.defaultMarker;
|
||||
}
|
||||
items.push({
|
||||
'label': signGroup.label,
|
||||
'checked': signGroup.checked,
|
||||
'icon': iconURL,
|
||||
'action': function(n, item, checked) {
|
||||
jQuery.each(overviewer.collections.markers[item.label],
|
||||
function(i, elem) {
|
||||
elem.setVisible(checked);
|
||||
}
|
||||
);
|
||||
overviewer.util.debug('Adding sign item: ' + item);
|
||||
}
|
||||
});
|
||||
}
|
||||
overviewer.util.createDropDown('Signposts', items);
|
||||
}
|
||||
|
||||
// if there are any regions data, lets show the option to hide/show them.
|
||||
if (overviewerConfig.objectGroups.regions.length > 0) {
|
||||
// region display control
|
||||
var items = [];
|
||||
for (i in overviewerConfig.objectGroups.regions) {
|
||||
var regionGroup = overviewerConfig.objectGroups.regions[i];
|
||||
items.push({
|
||||
'label': regionGroup.label,
|
||||
'checked': regionGroup.checked,
|
||||
'action': function(n, item, checked) {
|
||||
jQuery.each(overviewer.collections.regions[item.label],
|
||||
function(i,elem) {
|
||||
// Thanks to LeastWeasel for this line!
|
||||
elem.setMap(checked ? overviewer.map : null);
|
||||
});
|
||||
overviewer.util.debug('Adding region item: ' + item);
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
overviewer.util.createDropDown('Regions', items);
|
||||
}
|
||||
|
||||
if (overviewer.collections.overlays.length > 0) {
|
||||
// overlay maps control
|
||||
var items = [];
|
||||
for (i in overviewer.collections.overlays) {
|
||||
var overlay = overviewer.collections.overlays[i];
|
||||
items.push({
|
||||
'label': overlay.name,
|
||||
'checked': false,
|
||||
'overlay': overlay,
|
||||
'action': function(i, item, checked) {
|
||||
if (checked) {
|
||||
overviewer.map.overlayMapTypes.push(item.overlay);
|
||||
} else {
|
||||
var idx_to_delete = -1;
|
||||
overviewer.map.overlayMapTypes.forEach(function(e, j) {
|
||||
if (e == item.overlay) {
|
||||
idx_to_delete = j;
|
||||
}
|
||||
});
|
||||
if (idx_to_delete >= 0) {
|
||||
overviewer.map.overlayMapTypes.removeAt(idx_to_delete);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
overviewer.util.createDropDown('Overlays', items);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Reusable method for creating drop-down menus
|
||||
*
|
||||
* @param string title
|
||||
* @param array items
|
||||
*/
|
||||
'createDropDown': function(title, items) {
|
||||
var control = document.createElement('DIV');
|
||||
// let's let a style sheet do most of the styling here
|
||||
control.id = 'customControl';
|
||||
|
||||
var controlText = document.createElement('DIV');
|
||||
controlText.innerHTML = title;
|
||||
|
||||
var controlBorder = document.createElement('DIV');
|
||||
controlBorder.id='top';
|
||||
control.appendChild(controlBorder);
|
||||
controlBorder.appendChild(controlText);
|
||||
|
||||
var dropdownDiv = document.createElement('DIV');
|
||||
dropdownDiv.id='dropDown';
|
||||
control.appendChild(dropdownDiv);
|
||||
dropdownDiv.innerHTML='';
|
||||
|
||||
// add the functionality to toggle visibility of the items
|
||||
$(controlText).click(function() {
|
||||
$(dropdownDiv).toggle();
|
||||
});
|
||||
|
||||
// add that control box we've made back to the map.
|
||||
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(control);
|
||||
|
||||
for(i in items) {
|
||||
// create the visible elements of the item
|
||||
var item = items[i];
|
||||
overviewer.util.debug(item);
|
||||
var itemDiv = document.createElement('div');
|
||||
var itemInput = document.createElement('input');
|
||||
itemInput.type='checkbox';
|
||||
|
||||
// give it a name
|
||||
$(itemInput).data('label',item.label);
|
||||
jQuery(itemInput).click((function(local_idx, local_item) {
|
||||
return function(e) {
|
||||
item.action(local_idx, local_item, e.target.checked);
|
||||
};
|
||||
})(i, item));
|
||||
|
||||
// if its checked, its gotta do something, do that here.
|
||||
if (item.checked) {
|
||||
itemInput.checked = true;
|
||||
item.action(i, item, item.checked);
|
||||
}
|
||||
dropdownDiv.appendChild(itemDiv);
|
||||
itemDiv.appendChild(itemInput);
|
||||
var textNode = document.createElement('text');
|
||||
if(item.icon) {
|
||||
textNode.innerHTML = '<img width="15" height="15" src="' +
|
||||
item.icon + '">' + item.label + '<br/>';
|
||||
} else {
|
||||
textNode.innerHTML = item.label + '<br/>';
|
||||
}
|
||||
|
||||
itemDiv.appendChild(textNode);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Create the pop-up infobox for when you click on a region, this can't
|
||||
* be done in-line because of stupid Javascript scoping problems with
|
||||
* closures or something.
|
||||
*
|
||||
* @param google.maps.Polygon|google.maps.Polyline shape
|
||||
*/
|
||||
'createRegionInfoWindow': function(shape) {
|
||||
var infowindow = new google.maps.InfoWindow();
|
||||
google.maps.event.addListener(shape, 'click', function(event, i) {
|
||||
if (overviewer.collections.infoWindow) {
|
||||
overviewer.collections.infoWindow.close();
|
||||
}
|
||||
// Replace our Info Window's content and position
|
||||
var contentString = '<b>Region: ' + shape.name + '</b><br />' +
|
||||
'Clicked Location: <br />' + event.latLng.lat() + ', ' +
|
||||
event.latLng.lng() + '<br />';
|
||||
infowindow.setContent(contentString);
|
||||
infowindow.setPosition(event.latLng);
|
||||
infowindow.open(overviewer.map);
|
||||
overviewer.collections.infoWindow = infowindow;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Same as createRegionInfoWindow()
|
||||
*
|
||||
* @param google.maps.Marker marker
|
||||
*/
|
||||
'createMarkerInfoWindow': function(marker) {
|
||||
var windowContent = '<div class="infoWindow"><img src="' + marker.icon +
|
||||
'"/><p>' + marker.title.replace(/\n/g,'<br/>') + '</p></div>';
|
||||
var infowindow = new google.maps.InfoWindow({
|
||||
'content': windowContent
|
||||
});
|
||||
google.maps.event.addListener(marker, 'click', function() {
|
||||
if (overviewer.collections.infoWindow) {
|
||||
overviewer.collections.infoWindow.close();
|
||||
}
|
||||
infowindow.open(overviewer.map, marker);
|
||||
overviewer.collections.infoWindow = infowindow;
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* The various classes needed in this file.
|
||||
*/
|
||||
'classes': {
|
||||
/**
|
||||
* This is the button that centers the map on spawn. Not sure why we
|
||||
* need a separate class for this and not some of the other controls.
|
||||
*
|
||||
* @param documentElement controlDiv
|
||||
*/
|
||||
'HomeControl': function(controlDiv) {
|
||||
controlDiv.style.padding = '5px';
|
||||
// Set CSS for the control border
|
||||
var control = document.createElement('DIV');
|
||||
control.id='top';
|
||||
control.title = 'Click to center the map on the Spawn';
|
||||
controlDiv.appendChild(control);
|
||||
|
||||
// Set CSS for the control interior
|
||||
var controlText = document.createElement('DIV');
|
||||
controlText.innerHTML = 'Spawn';
|
||||
controlText.id='button';
|
||||
control.appendChild(controlText);
|
||||
|
||||
// Setup the click event listeners: simply set the map to map center
|
||||
// as definned below
|
||||
google.maps.event.addDomListener(control, 'click', function() {
|
||||
overviewer.map.panTo(overviewer.util.fromWorldToLatLng(
|
||||
overviewerConfig.map.center[0],
|
||||
overviewerConfig.map.center[1],
|
||||
overviewerConfig.map.center[2]));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Our custom projection maps Latitude to Y, and Longitude to X as
|
||||
* normal, but it maps the range [0.0, 1.0] to [0, tileSize] in both
|
||||
* directions so it is easier to position markers, etc. based on their
|
||||
* position (find their position in the lowest-zoom image, and divide
|
||||
* by tileSize)
|
||||
*/
|
||||
'MapProjection' : function() {
|
||||
this.inverseTileSize = 1.0 / overviewerConfig.CONST.tileSize;
|
||||
},
|
||||
/**
|
||||
* This is a mapType used only for debugging, to draw a grid on the screen
|
||||
* showing the tile co-ordinates and tile path. Currently the tile path
|
||||
* part does not work.
|
||||
*
|
||||
* @param google.maps.Size tileSize
|
||||
*/
|
||||
'CoordMapType': function(tileSize) {
|
||||
this.tileSize = tileSize;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Stuff that we give to the google maps code instead of using ourselves
|
||||
* goes in here.
|
||||
*
|
||||
* Also, why do I keep writing these comments as if I'm multiple people? I
|
||||
* should probably stop that.
|
||||
*/
|
||||
'gmap': {
|
||||
/**
|
||||
* Generate a function to get the path to a tile at a particular location
|
||||
* and zoom level.
|
||||
*
|
||||
* @param string path
|
||||
* @param string pathBase
|
||||
* @param string pathExt
|
||||
*/
|
||||
'getTileUrlGenerator': function(path, pathBase, pathExt) {
|
||||
return function(tile, zoom) {
|
||||
var url = path;
|
||||
var urlBase = ( pathBase ? pathBase : '' );
|
||||
if(tile.x < 0 || tile.x >= Math.pow(2, zoom) ||
|
||||
tile.y < 0 || tile.y >= Math.pow(2, zoom)) {
|
||||
url += '/blank';
|
||||
} else if(zoom == 0) {
|
||||
url += '/base';
|
||||
} else {
|
||||
for(var z = zoom - 1; z >= 0; --z) {
|
||||
var x = Math.floor(tile.x / Math.pow(2, z)) % 2;
|
||||
var y = Math.floor(tile.y / Math.pow(2, z)) % 2;
|
||||
url += '/' + (x + 2 * y);
|
||||
}
|
||||
}
|
||||
url = url + '.' + pathExt;
|
||||
if(overviewerConfig.map.cacheMinutes > 0) {
|
||||
var d = new Date();
|
||||
url += '?c=' + Math.floor(d.getTime() /
|
||||
(1000 * 60 * overviewerConfig.map.cacheMinutes));
|
||||
}
|
||||
return(urlBase + url);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -23,6 +23,7 @@ from time import strftime, gmtime
|
||||
import json
|
||||
|
||||
import util
|
||||
from c_overviewer import get_render_mode_inheritance
|
||||
|
||||
"""
|
||||
This module has routines related to generating a Google Maps-based
|
||||
@@ -59,25 +60,25 @@ def mirror_dir(src, dst, entities=None):
|
||||
# if this stills throws an error, let it propagate up
|
||||
|
||||
class MapGen(object):
|
||||
def __init__(self, quadtrees, skipjs=False, web_assets_hook=None):
|
||||
def __init__(self, quadtrees, configInfo):
|
||||
"""Generates a Google Maps interface for the given list of
|
||||
quadtrees. All of the quadtrees must have the same destdir,
|
||||
image format, and world.
|
||||
Note:tiledir for each quadtree should be unique. By default the tiledir is determined by the rendermode"""
|
||||
|
||||
self.skipjs = skipjs
|
||||
self.web_assets_hook = web_assets_hook
|
||||
self.skipjs = configInfo.get('skipjs', None)
|
||||
self.web_assets_hook = configInfo.get('web_assets_hook', None)
|
||||
self.bg_color = configInfo.get('bg_color')
|
||||
|
||||
if not len(quadtrees) > 0:
|
||||
raise ValueError("there must be at least one quadtree to work on")
|
||||
|
||||
self.destdir = quadtrees[0].destdir
|
||||
self.imgformat = quadtrees[0].imgformat
|
||||
self.world = quadtrees[0].world
|
||||
self.p = quadtrees[0].p
|
||||
for i in quadtrees:
|
||||
if i.destdir != self.destdir or i.imgformat != self.imgformat or i.world != self.world:
|
||||
raise ValueError("all the given quadtrees must have the same destdir")
|
||||
if i.destdir != self.destdir or i.world != self.world:
|
||||
raise ValueError("all the given quadtrees must have the same destdir and world")
|
||||
|
||||
self.quadtrees = quadtrees
|
||||
|
||||
@@ -85,32 +86,38 @@ class MapGen(object):
|
||||
"""Writes out config.js, marker.js, and region.js
|
||||
Copies web assets into the destdir"""
|
||||
zoomlevel = self.p
|
||||
imgformat = self.imgformat
|
||||
configpath = os.path.join(util.get_program_path(), "config.js")
|
||||
configpath = os.path.join(util.get_program_path(), "overviewerConfig.js")
|
||||
|
||||
config = open(configpath, 'r').read()
|
||||
config = config.replace(
|
||||
"{maxzoom}", str(zoomlevel))
|
||||
"{minzoom}", str(0))
|
||||
config = config.replace(
|
||||
"{imgformat}", str(imgformat))
|
||||
"{maxzoom}", str(zoomlevel))
|
||||
|
||||
config = config.replace("{spawn_coords}",
|
||||
json.dumps(list(self.world.spawn)))
|
||||
|
||||
#config = config.replace("{bg_color}", self.bg_color)
|
||||
|
||||
# create generated map type data, from given quadtrees
|
||||
maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(),
|
||||
'path' : q.tiledir}, self.quadtrees)
|
||||
'path' : q.tiledir,
|
||||
'bg_color': self.bg_color,
|
||||
'overlay' : 'overlay' in get_render_mode_inheritance(q.rendermode),
|
||||
'imgformat' : q.imgformat},
|
||||
self.quadtrees)
|
||||
config = config.replace("{maptypedata}", json.dumps(maptypedata))
|
||||
|
||||
with open(os.path.join(self.destdir, "config.js"), 'w') as output:
|
||||
with open(os.path.join(self.destdir, "overviewerConfig.js"), 'w') as output:
|
||||
output.write(config)
|
||||
|
||||
bgcolor = (int(self.bg_color[1:3],16), int(self.bg_color[3:5],16), int(self.bg_color[5:7],16), 0)
|
||||
blank = Image.new("RGBA", (1,1), bgcolor)
|
||||
# Write a blank image
|
||||
for quadtree in self.quadtrees:
|
||||
blank = Image.new("RGBA", (1,1))
|
||||
tileDir = os.path.join(self.destdir, quadtree.tiledir)
|
||||
if not os.path.exists(tileDir): os.mkdir(tileDir)
|
||||
blank.save(os.path.join(tileDir, "blank."+self.imgformat))
|
||||
blank.save(os.path.join(tileDir, "blank."+quadtree.imgformat))
|
||||
|
||||
# copy web assets into destdir:
|
||||
mirror_dir(os.path.join(util.get_program_path(), "web_assets"), self.destdir)
|
||||
@@ -144,13 +151,13 @@ class MapGen(object):
|
||||
|
||||
# write out the default marker table
|
||||
with open(os.path.join(self.destdir, "markers.js"), 'w') as output:
|
||||
output.write("var markerData=[\n")
|
||||
output.write("overviewer.collections.markerDatas.push([\n")
|
||||
for marker in self.world.POI:
|
||||
output.write(json.dumps(marker))
|
||||
if marker != self.world.POI[-1]:
|
||||
output.write(",")
|
||||
output.write("\n")
|
||||
output.write("]\n")
|
||||
output.write("]);\n")
|
||||
|
||||
# save persistent data
|
||||
self.world.persistentData['POI'] = self.world.POI
|
||||
@@ -159,11 +166,11 @@ class MapGen(object):
|
||||
|
||||
# write out the default (empty, but documented) region table
|
||||
with open(os.path.join(self.destdir, "regions.js"), 'w') as output:
|
||||
output.write('var regionData=[\n')
|
||||
output.write('overviewer.collections.regionDatas.push([\n')
|
||||
output.write(' // {"color": "#FFAA00", "opacity": 0.5, "closed": true, "path": [\n')
|
||||
output.write(' // {"x": 0, "y": 0, "z": 0},\n')
|
||||
output.write(' // {"x": 0, "y": 10, "z": 0},\n')
|
||||
output.write(' // {"x": 0, "y": 0, "z": 10}\n')
|
||||
output.write(' // ]},\n')
|
||||
output.write('];')
|
||||
output.write(']);')
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ from PIL import Image
|
||||
|
||||
import nbt
|
||||
import chunk
|
||||
from c_overviewer import get_render_mode_inheritance
|
||||
from optimizeimages import optimize_image
|
||||
import composite
|
||||
|
||||
@@ -48,7 +49,7 @@ def iterate_base4(d):
|
||||
return itertools.product(xrange(4), repeat=d)
|
||||
|
||||
class QuadtreeGen(object):
|
||||
def __init__(self, worldobj, destdir, depth=None, tiledir=None, imgformat=None, optimizeimg=None, rendermode="normal"):
|
||||
def __init__(self, worldobj, destdir, bgcolor, depth=None, tiledir=None, imgformat=None, imgquality=95, optimizeimg=None, rendermode="normal"):
|
||||
"""Generates a quadtree from the world given into the
|
||||
given dest directory
|
||||
|
||||
@@ -60,16 +61,18 @@ class QuadtreeGen(object):
|
||||
"""
|
||||
assert(imgformat)
|
||||
self.imgformat = imgformat
|
||||
self.imgquality = imgquality
|
||||
self.optimizeimg = optimizeimg
|
||||
|
||||
self.lighting = rendermode in ("lighting", "night", "spawn")
|
||||
self.night = rendermode in ("night", "spawn")
|
||||
self.spawn = rendermode in ("spawn",)
|
||||
self.bgcolor = bgcolor
|
||||
self.rendermode = rendermode
|
||||
|
||||
# force png renderformat if we're using an overlay mode
|
||||
if 'overlay' in get_render_mode_inheritance(rendermode):
|
||||
self.imgformat = "png"
|
||||
|
||||
# Make the destination dir
|
||||
if not os.path.exists(destdir):
|
||||
os.mkdir(destdir)
|
||||
os.makedirs(os.path.abspath(destdir))
|
||||
if tiledir is None:
|
||||
tiledir = rendermode
|
||||
self.tiledir = tiledir
|
||||
@@ -89,7 +92,7 @@ class QuadtreeGen(object):
|
||||
yradius >= worldobj.maxrow and -yradius <= worldobj.minrow:
|
||||
break
|
||||
else:
|
||||
raise ValueError("Your map is waaaay too big! Use the '-z' or '--zoom' options.")
|
||||
raise ValueError("Your map is waaaay too big! Use the 'zoom' option in 'settings.py'.")
|
||||
|
||||
self.p = p
|
||||
else:
|
||||
@@ -113,10 +116,10 @@ class QuadtreeGen(object):
|
||||
returns -1 if it couldn't be detected, file not found, or nothing in
|
||||
config.js matched
|
||||
"""
|
||||
indexfile = os.path.join(self.destdir, "config.js")
|
||||
indexfile = os.path.join(self.destdir, "overviewerConfig.js")
|
||||
if not os.path.exists(indexfile):
|
||||
return -1
|
||||
matcher = re.compile(r"maxZoom:\s*(\d+)")
|
||||
matcher = re.compile(r"maxZoom.*:\s*(\d+)")
|
||||
p = -1
|
||||
for line in open(indexfile, "r"):
|
||||
res = matcher.search(line)
|
||||
@@ -320,7 +323,7 @@ class QuadtreeGen(object):
|
||||
#logging.debug("writing out innertile {0}".format(imgpath))
|
||||
|
||||
# Create the actual image now
|
||||
img = Image.new("RGBA", (384, 384), (38,92,255,0))
|
||||
img = Image.new("RGBA", (384, 384), self.bgcolor)
|
||||
|
||||
# we'll use paste (NOT alpha_over) for quadtree generation because
|
||||
# this is just straight image stitching, not alpha blending
|
||||
@@ -334,7 +337,7 @@ class QuadtreeGen(object):
|
||||
|
||||
# Save it
|
||||
if self.imgformat == 'jpg':
|
||||
img.save(imgpath, quality=95, subsampling=0)
|
||||
img.save(imgpath, quality=self.imgquality, subsampling=0)
|
||||
else: # png
|
||||
img.save(imgpath)
|
||||
|
||||
@@ -442,7 +445,7 @@ class QuadtreeGen(object):
|
||||
#logging.debug("writing out worldtile {0}".format(imgpath))
|
||||
|
||||
# Compile this image
|
||||
tileimg = Image.new("RGBA", (width, height), (38,92,255,0))
|
||||
tileimg = Image.new("RGBA", (width, height), self.bgcolor)
|
||||
|
||||
world = self.world
|
||||
rendermode = self.rendermode
|
||||
@@ -461,6 +464,9 @@ class QuadtreeGen(object):
|
||||
pass
|
||||
|
||||
# Save them
|
||||
if self.imgformat == 'jpg':
|
||||
tileimg.save(imgpath, quality=self.imgquality, subsampling=0)
|
||||
else: # png
|
||||
tileimg.save(imgpath)
|
||||
|
||||
if self.optimizeimg:
|
||||
|
||||
@@ -149,7 +149,6 @@ class RenderNode(object):
|
||||
total += 4**q.p
|
||||
if q.p > max_p:
|
||||
max_p = q.p
|
||||
q.go(procs)
|
||||
self.max_p = max_p
|
||||
# Render the highest level of tiles from the chunks
|
||||
results = collections.deque()
|
||||
|
||||
@@ -273,7 +273,8 @@ alpha_over_wrap(PyObject *self, PyObject *args)
|
||||
* also, it multiplies instead of doing an over operation
|
||||
*/
|
||||
PyObject *
|
||||
tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb,
|
||||
tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg,
|
||||
unsigned char sb, unsigned char sa,
|
||||
PyObject *mask, int dx, int dy, int xsize, int ysize) {
|
||||
/* libImaging handles */
|
||||
Imaging imDest, imMask;
|
||||
@@ -332,9 +333,11 @@ tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char
|
||||
out++;
|
||||
*out = MULDIV255(*out, sb, tmp1);
|
||||
out++;
|
||||
*out = MULDIV255(*out, sa, tmp1);
|
||||
out++;
|
||||
} else if (*inmask == 0) {
|
||||
/* do nothing -- source is fully transparent */
|
||||
out += 3;
|
||||
out += 4;
|
||||
} else {
|
||||
/* general case */
|
||||
|
||||
@@ -345,9 +348,10 @@ tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char
|
||||
out++;
|
||||
*out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sb, *inmask, tmp1), tmp2);
|
||||
out++;
|
||||
*out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sa, *inmask, tmp1), tmp2);
|
||||
out++;
|
||||
}
|
||||
|
||||
out++;
|
||||
inmask += mask_stride;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,12 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) {
|
||||
int x = state->x, y = state->y, z = state->z;
|
||||
unsigned char data = 0;
|
||||
|
||||
if (state->block == 9) { /* water */
|
||||
if (state->block == 2) { /* grass */
|
||||
/* return 0x10 if grass is covered in snow */
|
||||
if (z < 127 && getArrayByte3D(state->blocks, x, y, z+1) == 78)
|
||||
return 0x10;
|
||||
return ancilData;
|
||||
} else if (state->block == 9) { /* water */
|
||||
/* an aditional bit for top is added to the 4 bits of check_adjacent_blocks */
|
||||
if ((ancilData == 0) || (ancilData >= 10)) { /* static water, only top, and unkown ancildata values */
|
||||
data = 16;
|
||||
@@ -208,8 +213,74 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) {
|
||||
final_data = final_data | 0x10;
|
||||
}
|
||||
return final_data;
|
||||
|
||||
} else if (state-> block == 54) { /* chests */
|
||||
/* the top 2 bits are used to store the type of chest
|
||||
* (single or double), the 2 bottom bits are used for
|
||||
* orientation, look textures.py for more information. */
|
||||
|
||||
/* if placed alone chests always face west, return 0 to make a
|
||||
* chest facing west */
|
||||
unsigned char chest_data = 0, air_data = 0, final_data = 0;
|
||||
|
||||
/* search for chests */
|
||||
chest_data = check_adjacent_blocks(state, x, y, z, 54);
|
||||
|
||||
/* search for air */
|
||||
air_data = check_adjacent_blocks(state, x, y, z, 0);
|
||||
|
||||
if (chest_data == 1) { /* another chest in the east */
|
||||
final_data = final_data | 0x8; /* only can face to north or south */
|
||||
if ( (air_data & 0x2) == 2 ) {
|
||||
final_data = final_data | 0x1; /* facing north */
|
||||
} else {
|
||||
final_data = final_data | 0x3; /* facing south */
|
||||
}
|
||||
|
||||
} else if (chest_data == 2) { /* in the north */
|
||||
final_data = final_data | 0x4; /* only can face to east or west */
|
||||
if ( !((air_data & 0x4) == 4) ) { /* 0 = west */
|
||||
final_data = final_data | 0x2; /* facing east */
|
||||
}
|
||||
|
||||
} else if (chest_data == 4) { /*in the west */
|
||||
final_data = final_data | 0x4;
|
||||
if ( (air_data & 0x2) == 2 ) {
|
||||
final_data = final_data | 0x1; /* facing north */
|
||||
} else {
|
||||
final_data = final_data | 0x3; /* facing south */
|
||||
}
|
||||
|
||||
} else if (chest_data == 8) { /*in the south */
|
||||
final_data = final_data | 0x8;
|
||||
if ( !((air_data & 0x4) == 4) ) {
|
||||
final_data = final_data | 0x2; /* facing east */
|
||||
}
|
||||
|
||||
} else if (chest_data == 0) {
|
||||
/* Single chest, determine the orientation */
|
||||
if ( ((air_data & 0x8) == 0) && ((air_data & 0x2) == 2) ) { /* block in +x and no block in -x */
|
||||
final_data = final_data | 0x1; /* facing north */
|
||||
|
||||
} else if ( ((air_data & 0x2) == 0) && ((air_data & 0x8) == 8)) {
|
||||
final_data = final_data | 0x3;
|
||||
|
||||
} else if ( ((air_data & 0x4) == 0) && ((air_data & 0x1) == 1)) {
|
||||
final_data = final_data | 0x2;
|
||||
} /* else, facing west, value = 0 */
|
||||
|
||||
} else {
|
||||
/* more than one adjacent chests! render as normal chest */
|
||||
return 0;
|
||||
}
|
||||
|
||||
return final_data;
|
||||
|
||||
} else if (state->block == 90) {
|
||||
return check_adjacent_blocks(state, x, y, z, state->block);
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
|
||||
}
|
||||
@@ -329,7 +400,7 @@ chunk_render(PyObject *self, PyObject *args) {
|
||||
PyObject *tmp;
|
||||
|
||||
unsigned char ancilData = getArrayByte3D(blockdata_expanded, state.x, state.y, state.z);
|
||||
if ((state.block == 85) || (state.block == 9) || (state.block == 55)) {
|
||||
if ((state.block == 85) || (state.block == 9) || (state.block == 55) || (state.block == 54) || (state.block == 2) || (state.block == 90)) {
|
||||
ancilData = generate_pseudo_data(&state, ancilData);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,24 @@ PyObject *get_extension_version(PyObject *self, PyObject *args) {
|
||||
static PyMethodDef COverviewerMethods[] = {
|
||||
{"alpha_over", alpha_over_wrap, METH_VARARGS,
|
||||
"alpha over composite function"},
|
||||
|
||||
{"render_loop", chunk_render, METH_VARARGS,
|
||||
"Renders stuffs"},
|
||||
|
||||
{"get_render_modes", get_render_modes, METH_VARARGS,
|
||||
"returns available render modes"},
|
||||
{"get_render_mode_info", get_render_mode_info, METH_VARARGS,
|
||||
"returns info for a particular render mode"},
|
||||
{"get_render_mode_parent", get_render_mode_parent, METH_VARARGS,
|
||||
"returns parent for a particular render mode"},
|
||||
{"get_render_mode_inheritance", get_render_mode_inheritance, METH_VARARGS,
|
||||
"returns inheritance chain for a particular render mode"},
|
||||
{"get_render_mode_children", get_render_mode_children, METH_VARARGS,
|
||||
"returns (direct) children for a particular render mode"},
|
||||
|
||||
{"extension_version", get_extension_version, METH_VARARGS,
|
||||
"Returns the extension version"},
|
||||
|
||||
{NULL, NULL, 0, NULL} /* Sentinel */
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
// increment this value if you've made a change to the c extesion
|
||||
// and want to force users to rebuild
|
||||
#define OVERVIEWER_EXTENSION_VERSION 3
|
||||
#define OVERVIEWER_EXTENSION_VERSION 5
|
||||
|
||||
/* Python PIL, and numpy headers */
|
||||
#include <Python.h>
|
||||
@@ -48,7 +48,8 @@ PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask,
|
||||
PyObject *alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alpha,
|
||||
int dx, int dy, int xsize, int ysize);
|
||||
PyObject *alpha_over_wrap(PyObject *self, PyObject *args);
|
||||
PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb,
|
||||
PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg,
|
||||
unsigned char sb, unsigned char sa,
|
||||
PyObject *mask, int dx, int dy, int xsize, int ysize);
|
||||
|
||||
/* in iterate.c */
|
||||
|
||||
234
overviewer_core/src/rendermode-cave.c
Normal file
234
overviewer_core/src/rendermode-cave.c
Normal file
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "overviewer.h"
|
||||
#include <math.h>
|
||||
//~
|
||||
//~ /* figures out the black_coeff from a given skylight and blocklight, used in
|
||||
//~ lighting calculations -- note this is *different* from the one in
|
||||
//~ rendermode-lighting.c (the "skylight - 11" part) */
|
||||
//~ static float calculate_darkness(unsigned char skylight, unsigned char blocklight) {
|
||||
//~ return 1.0f - powf(0.8f, 15.0 - MAX(blocklight, skylight - 11));
|
||||
//~ }
|
||||
|
||||
static int
|
||||
rendermode_cave_occluded(void *data, RenderState *state) {
|
||||
int x = state->x, y = state->y, z = state->z, dz = 0;
|
||||
RenderModeCave* self;
|
||||
self = (RenderModeCave *)data;
|
||||
|
||||
/* check if the block is touching skylight */
|
||||
if (z != 127) {
|
||||
|
||||
if (getArrayByte3D(self->skylight, x, y, z+1) != 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ((x == 15)) {
|
||||
if (self->up_right_skylight != Py_None) {
|
||||
if (getArrayByte3D(self->up_right_skylight, 0, y, z) != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (getArrayByte3D(self->skylight, x+1, y, z) != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (x == 0) {
|
||||
if (self->left_skylight != Py_None) {
|
||||
if (getArrayByte3D(self->left_skylight, 15, y, z) != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (getArrayByte3D(self->skylight, x-1, y, z) != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (y == 15) {
|
||||
if (self->right_skylight != Py_None) {
|
||||
if (getArrayByte3D(self->right_skylight, 0, y, z) != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (getArrayByte3D(self->skylight, x, y+1, z) != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (y == 0) {
|
||||
if (self->up_left_skylight != Py_None) {
|
||||
if (getArrayByte3D(self->up_left_skylight, 15, y, z) != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (getArrayByte3D(self->skylight, x, y-1, z) != 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* check for normal occlusion */
|
||||
/* use ajacent chunks, if not you get blocks spreaded in chunk edges */
|
||||
if ( (x == 0) && (y != 15) ) {
|
||||
if (state->left_blocks != Py_None) {
|
||||
if (!is_transparent(getArrayByte3D(state->left_blocks, 15, y, z)) &&
|
||||
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) &&
|
||||
!is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (x != 0) && (y == 15) ) {
|
||||
if (state->right_blocks != Py_None) {
|
||||
if (!is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) &&
|
||||
!is_transparent(getArrayByte3D(state->right_blocks, x, 0, z)) &&
|
||||
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1))) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (x == 0) && (y == 15) ) {
|
||||
if ((state->left_blocks != Py_None) &&
|
||||
(state->right_blocks != Py_None)) {
|
||||
if (!is_transparent(getArrayByte3D(state->left_blocks, 15, y, z)) &&
|
||||
!is_transparent(getArrayByte3D(state->right_blocks, x, 0, z)) &&
|
||||
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1))) {
|
||||
return 1;
|
||||
}
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( (x != 0) && (y != 15) &&
|
||||
!is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) &&
|
||||
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) &&
|
||||
!is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
} else { /* if z == 127 skip */
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* check for lakes and seas and don't render them */
|
||||
/* at this point of the code the block has no skylight
|
||||
* and is not occluded, but a deep sea can fool these
|
||||
* 2 tests */
|
||||
|
||||
if ((getArrayByte3D(state->blocks, x, y, z) == 9) ||
|
||||
(getArrayByte3D(state->blocks, x, y, z+1) == 9)) {
|
||||
|
||||
for (dz = z+1; dz < 127; dz++) { /* go up and check for skylight */
|
||||
if (getArrayByte3D(self->skylight, x, y, dz) != 0) {
|
||||
return 1;
|
||||
}
|
||||
if (getArrayByte3D(state->blocks, x, y, dz) != 9) {
|
||||
/* we are out of the water! and there's no skylight
|
||||
* , i.e. is a cave lake or something similar */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
rendermode_cave_start(void *data, RenderState *state) {
|
||||
RenderModeCave* self;
|
||||
int ret;
|
||||
self = (RenderModeCave *)data;
|
||||
|
||||
/* first, chain up */
|
||||
ret = rendermode_normal.start(data, state);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
/* if there's skylight we are in the surface! */
|
||||
self->skylight = PyObject_GetAttrString(state->self, "skylight");
|
||||
self->left_skylight = PyObject_GetAttrString(state->self, "left_skylight");
|
||||
self->right_skylight = PyObject_GetAttrString(state->self, "right_skylight");
|
||||
self->up_left_skylight = PyObject_GetAttrString(state->self, "up_left_skylight");
|
||||
self->up_right_skylight = PyObject_GetAttrString(state->self, "up_right_skylight");
|
||||
|
||||
/* colors for tinting */
|
||||
self->depth_colors = PyObject_GetAttrString(state->chunk, "depth_colors");
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
rendermode_cave_finish(void *data, RenderState *state) {
|
||||
RenderModeCave* self;
|
||||
self = (RenderModeCave *)data;
|
||||
|
||||
Py_DECREF(self->skylight);
|
||||
Py_DECREF(self->left_skylight);
|
||||
Py_DECREF(self->right_skylight);
|
||||
Py_DECREF(self->up_left_skylight);
|
||||
Py_DECREF(self->up_right_skylight);
|
||||
|
||||
Py_DECREF(self->depth_colors);
|
||||
|
||||
rendermode_normal.finish(data, state);
|
||||
}
|
||||
|
||||
static void
|
||||
rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) {
|
||||
RenderModeCave* self;
|
||||
int z, r, g, b;
|
||||
self = (RenderModeCave *)data;
|
||||
|
||||
z = state->z;
|
||||
r = 0, g = 0, b = 0;
|
||||
|
||||
/* draw the normal block */
|
||||
rendermode_normal.draw(data, state, src, mask);
|
||||
|
||||
/* get the colors and tint and tint */
|
||||
/* TODO TODO for a nether mode there isn't tinting! */
|
||||
r = PyInt_AsLong(PyList_GetItem(self->depth_colors, 0 + z*3));
|
||||
g = PyInt_AsLong(PyList_GetItem(self->depth_colors, 1 + z*3));
|
||||
b = PyInt_AsLong(PyList_GetItem(self->depth_colors, 2 + z*3));
|
||||
|
||||
tint_with_mask(state->img, r, g, b, 255, mask, state->imgx, state->imgy, 0, 0);
|
||||
|
||||
}
|
||||
|
||||
RenderModeInterface rendermode_cave = {
|
||||
"cave", "render only caves in normal mode",
|
||||
&rendermode_normal,
|
||||
sizeof(RenderModeCave),
|
||||
rendermode_cave_start,
|
||||
rendermode_cave_finish,
|
||||
rendermode_cave_occluded,
|
||||
rendermode_cave_draw,
|
||||
};
|
||||
@@ -229,6 +229,7 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject
|
||||
|
||||
RenderModeInterface rendermode_lighting = {
|
||||
"lighting", "draw shadows from the lighting data",
|
||||
&rendermode_normal,
|
||||
sizeof(RenderModeLighting),
|
||||
rendermode_lighting_start,
|
||||
rendermode_lighting_finish,
|
||||
|
||||
@@ -61,6 +61,7 @@ rendermode_night_draw(void *data, RenderState *state, PyObject *src, PyObject *m
|
||||
|
||||
RenderModeInterface rendermode_night = {
|
||||
"night", "like \"lighting\", except at night",
|
||||
&rendermode_lighting,
|
||||
sizeof(RenderModeNight),
|
||||
rendermode_night_start,
|
||||
rendermode_night_finish,
|
||||
|
||||
@@ -120,17 +120,8 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
|
||||
RenderModeNormal *self = (RenderModeNormal *)data;
|
||||
|
||||
/* first, check to see if we should use biome-compatible src, mask */
|
||||
if (self->biome_data) {
|
||||
switch (state->block) {
|
||||
case 2:
|
||||
src = mask = self->grass_texture;
|
||||
break;
|
||||
case 18:
|
||||
if (self->biome_data && state->block == 18) {
|
||||
src = mask = self->leaf_texture;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
};
|
||||
}
|
||||
|
||||
/* draw the block! */
|
||||
@@ -147,9 +138,12 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
|
||||
|
||||
switch (state->block) {
|
||||
case 2:
|
||||
/* grass */
|
||||
/* grass -- skip for snowgrass */
|
||||
if (state->z < 127 && getArrayByte3D(state->blocks, state->x, state->y, state->z+1) == 78)
|
||||
break;
|
||||
color = PySequence_GetItem(self->grasscolor, index);
|
||||
facemask = self->facemask_top;
|
||||
facemask = self->grass_texture;
|
||||
alpha_over(state->img, self->grass_texture, self->grass_texture, state->imgx, state->imgy, 0, 0);
|
||||
break;
|
||||
case 18:
|
||||
/* leaves */
|
||||
@@ -169,7 +163,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
|
||||
b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2));
|
||||
Py_DECREF(color);
|
||||
|
||||
tint_with_mask(state->img, r, g, b, facemask, state->imgx, state->imgy, 0, 0);
|
||||
tint_with_mask(state->img, r, g, b, 255, facemask, state->imgx, state->imgy, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,7 +177,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
|
||||
int increment=0;
|
||||
if (state->block == 44) // half-step
|
||||
increment=6;
|
||||
else if (state->block == 78) // snow
|
||||
else if ((state->block == 78) || (state->block == 93) || (state->block == 94)) // snow, redstone repeaters (on and off)
|
||||
increment=9;
|
||||
|
||||
if ((state->x == 15) && (state->up_right_blocks != Py_None)) {
|
||||
@@ -221,6 +215,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
|
||||
|
||||
RenderModeInterface rendermode_normal = {
|
||||
"normal", "nothing special, just render the blocks",
|
||||
NULL,
|
||||
sizeof(RenderModeNormal),
|
||||
rendermode_normal_start,
|
||||
rendermode_normal_finish,
|
||||
|
||||
137
overviewer_core/src/rendermode-overlay.c
Normal file
137
overviewer_core/src/rendermode-overlay.c
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "overviewer.h"
|
||||
|
||||
static void get_color(void *data, RenderState *state,
|
||||
unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) {
|
||||
*r = 200;
|
||||
*g = 200;
|
||||
*b = 255;
|
||||
*a = 155;
|
||||
}
|
||||
|
||||
static int
|
||||
rendermode_overlay_start(void *data, RenderState *state) {
|
||||
PyObject *facemasks_py;
|
||||
RenderModeOverlay *self = (RenderModeOverlay *)data;
|
||||
|
||||
facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks");
|
||||
/* borrowed reference, needs to be incref'd if we keep it */
|
||||
self->facemask_top = PyTuple_GetItem(facemasks_py, 0);
|
||||
Py_INCREF(self->facemask_top);
|
||||
Py_DECREF(facemasks_py);
|
||||
|
||||
self->white_color = PyObject_GetAttrString(state->chunk, "white_color");
|
||||
|
||||
self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks");
|
||||
self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks");
|
||||
|
||||
self->get_color = get_color;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
rendermode_overlay_finish(void *data, RenderState *state) {
|
||||
RenderModeOverlay *self = (RenderModeOverlay *)data;
|
||||
|
||||
Py_DECREF(self->facemask_top);
|
||||
Py_DECREF(self->white_color);
|
||||
Py_DECREF(self->solid_blocks);
|
||||
Py_DECREF(self->fluid_blocks);
|
||||
}
|
||||
|
||||
static int
|
||||
rendermode_overlay_occluded(void *data, RenderState *state) {
|
||||
int x = state->x, y = state->y, z = state->z;
|
||||
|
||||
if ( (x != 0) && (y != 15) && (z != 127) &&
|
||||
!is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) &&
|
||||
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) &&
|
||||
!is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) {
|
||||
RenderModeOverlay *self = (RenderModeOverlay *)data;
|
||||
unsigned char r, g, b, a;
|
||||
PyObject *top_block_py, *block_py;
|
||||
|
||||
// exactly analogous to edge-line code for these special blocks
|
||||
int increment=0;
|
||||
if (state->block == 44) // half-step
|
||||
increment=6;
|
||||
else if (state->block == 78) // snow
|
||||
increment=9;
|
||||
|
||||
/* clear the draw space -- set alpha to 0 within mask */
|
||||
tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0);
|
||||
|
||||
/* skip rendering the overlay if we can't see it */
|
||||
if (state->z != 127) {
|
||||
unsigned char top_block = getArrayByte3D(state->blocks, state->x, state->y, state->z+1);
|
||||
if (!is_transparent(top_block)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* check to be sure this block is solid/fluid */
|
||||
top_block_py = PyInt_FromLong(top_block);
|
||||
if (PySequence_Contains(self->solid_blocks, top_block_py) ||
|
||||
PySequence_Contains(self->fluid_blocks, top_block_py)) {
|
||||
|
||||
/* top block is fluid or solid, skip drawing */
|
||||
Py_DECREF(top_block_py);
|
||||
return;
|
||||
}
|
||||
Py_DECREF(top_block_py);
|
||||
}
|
||||
|
||||
/* check to be sure this block is solid/fluid */
|
||||
block_py = PyInt_FromLong(state->block);
|
||||
if (!PySequence_Contains(self->solid_blocks, block_py) &&
|
||||
!PySequence_Contains(self->fluid_blocks, block_py)) {
|
||||
|
||||
/* not fluid or solid, skip drawing the overlay */
|
||||
Py_DECREF(block_py);
|
||||
return;
|
||||
}
|
||||
Py_DECREF(block_py);
|
||||
|
||||
/* get our color info */
|
||||
self->get_color(data, state, &r, &g, &b, &a);
|
||||
|
||||
/* do the overlay */
|
||||
if (a > 0) {
|
||||
alpha_over(state->img, self->white_color, self->facemask_top, state->imgx, state->imgy + increment, 0, 0);
|
||||
tint_with_mask(state->img, r, g, b, a, self->facemask_top, state->imgx, state->imgy + increment, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
RenderModeInterface rendermode_overlay = {
|
||||
"overlay", "base rendermode for informational overlays",
|
||||
NULL,
|
||||
sizeof(RenderModeOverlay),
|
||||
rendermode_overlay_start,
|
||||
rendermode_overlay_finish,
|
||||
rendermode_overlay_occluded,
|
||||
rendermode_overlay_draw,
|
||||
};
|
||||
@@ -18,21 +18,66 @@
|
||||
#include "overviewer.h"
|
||||
#include <math.h>
|
||||
|
||||
static void get_color(void *data, RenderState *state,
|
||||
unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) {
|
||||
|
||||
RenderModeSpawn* self = (RenderModeSpawn *)data;
|
||||
int x = state->x, y = state->y, z = state->z;
|
||||
int z_light = z + 1;
|
||||
unsigned char blocklight, skylight;
|
||||
PyObject *block_py;
|
||||
|
||||
/* set a nice, pretty red color */
|
||||
*r = 229;
|
||||
*g = 36;
|
||||
*b = 38;
|
||||
|
||||
/* default to no overlay, until told otherwise */
|
||||
*a = 0;
|
||||
|
||||
block_py = PyInt_FromLong(state->block);
|
||||
if (PySequence_Contains(self->nospawn_blocks, block_py)) {
|
||||
/* nothing can spawn on this */
|
||||
Py_DECREF(block_py);
|
||||
return;
|
||||
}
|
||||
Py_DECREF(block_py);
|
||||
|
||||
blocklight = getArrayByte3D(self->blocklight, x, y, MIN(127, z_light));
|
||||
|
||||
/* if we're at the top, force 15 (brightest!) skylight */
|
||||
if (z_light == 128) {
|
||||
skylight = 15;
|
||||
} else {
|
||||
skylight = getArrayByte3D(self->skylight, x, y, z_light);
|
||||
}
|
||||
|
||||
if (MAX(blocklight, skylight) <= 7) {
|
||||
/* hostile mobs spawn in daylight */
|
||||
*a = 240;
|
||||
} else if (MAX(blocklight, skylight - 11) <= 7) {
|
||||
/* hostile mobs spawn at night */
|
||||
*a = 150;
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
rendermode_spawn_start(void *data, RenderState *state) {
|
||||
RenderModeSpawn* self;
|
||||
|
||||
/* first, chain up */
|
||||
int ret = rendermode_night.start(data, state);
|
||||
int ret = rendermode_overlay.start(data, state);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
/* now do custom initializations */
|
||||
self = (RenderModeSpawn *)data;
|
||||
self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks");
|
||||
self->nospawn_blocks = PyObject_GetAttrString(state->chunk, "nospawn_blocks");
|
||||
self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks");
|
||||
self->red_color = PyObject_GetAttrString(state->chunk, "red_color");
|
||||
self->blocklight = PyObject_GetAttrString(state->self, "blocklight");
|
||||
self->skylight = PyObject_GetAttrString(state->self, "skylight");
|
||||
|
||||
/* setup custom color */
|
||||
self->parent.get_color = get_color;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -42,83 +87,29 @@ rendermode_spawn_finish(void *data, RenderState *state) {
|
||||
/* first free all *our* stuff */
|
||||
RenderModeSpawn* self = (RenderModeSpawn *)data;
|
||||
|
||||
Py_DECREF(self->solid_blocks);
|
||||
Py_DECREF(self->nospawn_blocks);
|
||||
Py_DECREF(self->fluid_blocks);
|
||||
Py_DECREF(self->blocklight);
|
||||
Py_DECREF(self->skylight);
|
||||
|
||||
/* now, chain up */
|
||||
rendermode_night.finish(data, state);
|
||||
rendermode_overlay.finish(data, state);
|
||||
}
|
||||
|
||||
static int
|
||||
rendermode_spawn_occluded(void *data, RenderState *state) {
|
||||
/* no special occlusion here */
|
||||
return rendermode_night.occluded(data, state);
|
||||
return rendermode_overlay.occluded(data, state);
|
||||
}
|
||||
|
||||
static void
|
||||
rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) {
|
||||
/* different versions of self (spawn, lighting) */
|
||||
RenderModeSpawn* self = (RenderModeSpawn *)data;
|
||||
RenderModeLighting *lighting = (RenderModeLighting *)self;
|
||||
|
||||
int x = state->x, y = state->y, z = state->z;
|
||||
PyObject *old_black_color = NULL;
|
||||
|
||||
/* figure out the appropriate darkness:
|
||||
this block for transparents, the block above for non-transparent */
|
||||
float darkness = 0.0;
|
||||
if (is_transparent(state->block)) {
|
||||
darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z, NULL);
|
||||
} else {
|
||||
darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z+1, NULL);
|
||||
}
|
||||
|
||||
/* if it's dark enough... */
|
||||
if (darkness > 0.8) {
|
||||
PyObject *block_py = PyInt_FromLong(state->block);
|
||||
|
||||
/* make sure it's solid */
|
||||
if (PySequence_Contains(self->solid_blocks, block_py)) {
|
||||
int spawnable = 1;
|
||||
|
||||
/* not spawnable if its in the nospawn list */
|
||||
if (PySequence_Contains(self->nospawn_blocks, block_py))
|
||||
spawnable = 0;
|
||||
|
||||
/* check the block above for solid or fluid */
|
||||
if (spawnable && z != 127) {
|
||||
PyObject *top_block_py = PyInt_FromLong(getArrayByte3D(state->blocks, x, y, z+1));
|
||||
if (PySequence_Contains(self->solid_blocks, top_block_py) ||
|
||||
PySequence_Contains(self->fluid_blocks, top_block_py)) {
|
||||
|
||||
spawnable = 0;
|
||||
}
|
||||
|
||||
Py_DECREF(top_block_py);
|
||||
}
|
||||
|
||||
/* if we passed all the checks, replace black_color with red_color */
|
||||
if (spawnable) {
|
||||
old_black_color = lighting->black_color;
|
||||
lighting->black_color = self->red_color;
|
||||
}
|
||||
}
|
||||
|
||||
Py_DECREF(block_py);
|
||||
}
|
||||
|
||||
/* draw normally */
|
||||
rendermode_night.draw(data, state, src, mask);
|
||||
|
||||
/* reset black_color, if needed */
|
||||
if (old_black_color != NULL) {
|
||||
lighting->black_color = old_black_color;
|
||||
}
|
||||
rendermode_overlay.draw(data, state, src, mask);
|
||||
}
|
||||
|
||||
RenderModeInterface rendermode_spawn = {
|
||||
"spawn", "draws red where monsters can spawn at night",
|
||||
"spawn", "draws a red overlay where monsters can spawn at night",
|
||||
&rendermode_overlay,
|
||||
sizeof(RenderModeSpawn),
|
||||
rendermode_spawn_start,
|
||||
rendermode_spawn_finish,
|
||||
|
||||
@@ -26,6 +26,7 @@ static RenderModeInterface *render_modes[] = {
|
||||
&rendermode_lighting,
|
||||
&rendermode_night,
|
||||
&rendermode_spawn,
|
||||
&rendermode_cave,
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -97,5 +98,87 @@ PyObject *get_render_mode_info(PyObject *self, PyObject *args) {
|
||||
}
|
||||
|
||||
Py_DECREF(info);
|
||||
return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode);
|
||||
}
|
||||
|
||||
/* bindings -- get parent's name */
|
||||
PyObject *get_render_mode_parent(PyObject *self, PyObject *args) {
|
||||
const char *rendermode;
|
||||
unsigned int i;
|
||||
if (!PyArg_ParseTuple(args, "s", &rendermode))
|
||||
return NULL;
|
||||
|
||||
for (i = 0; render_modes[i] != NULL; i++) {
|
||||
if (strcmp(render_modes[i]->name, rendermode) == 0) {
|
||||
if (render_modes[i]->parent) {
|
||||
/* has parent */
|
||||
return PyString_FromString(render_modes[i]->parent->name);
|
||||
} else {
|
||||
/* no parent */
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode);
|
||||
}
|
||||
|
||||
/* bindings -- get list of inherited parents */
|
||||
PyObject *get_render_mode_inheritance(PyObject *self, PyObject *args) {
|
||||
const char *rendermode;
|
||||
PyObject *parents;
|
||||
unsigned int i;
|
||||
RenderModeInterface *iface = NULL;
|
||||
if (!PyArg_ParseTuple(args, "s", &rendermode))
|
||||
return NULL;
|
||||
|
||||
parents = PyList_New(0);
|
||||
if (!parents)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; render_modes[i] != NULL; i++) {
|
||||
if (strcmp(render_modes[i]->name, rendermode) == 0) {
|
||||
iface = render_modes[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!iface) {
|
||||
Py_DECREF(parents);
|
||||
return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode);
|
||||
}
|
||||
|
||||
while (iface) {
|
||||
PyObject *name = PyString_FromString(iface->name);
|
||||
PyList_Append(parents, name);
|
||||
Py_DECREF(name);
|
||||
|
||||
iface = iface->parent;
|
||||
}
|
||||
|
||||
PyList_Reverse(parents);
|
||||
return parents;
|
||||
}
|
||||
|
||||
/* bindings -- get list of (direct) children */
|
||||
PyObject *get_render_mode_children(PyObject *self, PyObject *args) {
|
||||
const char *rendermode;
|
||||
PyObject *children;
|
||||
unsigned int i;
|
||||
if (!PyArg_ParseTuple(args, "s", &rendermode))
|
||||
return NULL;
|
||||
|
||||
children = PyList_New(0);
|
||||
if (!children)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; render_modes[i] != NULL; i++) {
|
||||
if (render_modes[i]->parent && strcmp(render_modes[i]->parent->name, rendermode) == 0) {
|
||||
PyObject *child_name = PyString_FromString(render_modes[i]->name);
|
||||
PyList_Append(children, child_name);
|
||||
Py_DECREF(child_name);
|
||||
}
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
@@ -38,12 +38,15 @@
|
||||
#include <Python.h>
|
||||
|
||||
/* rendermode interface */
|
||||
typedef struct {
|
||||
typedef struct _RenderModeInterface RenderModeInterface;
|
||||
struct _RenderModeInterface {
|
||||
/* the name of this mode */
|
||||
const char* name;
|
||||
/* the short description of this render mode */
|
||||
const char* description;
|
||||
|
||||
/* the rendermode this is derived from, or NULL */
|
||||
RenderModeInterface *parent;
|
||||
/* the size of the local storage for this rendermode */
|
||||
unsigned int data_size;
|
||||
|
||||
@@ -54,13 +57,16 @@ typedef struct {
|
||||
int (*occluded)(void *, RenderState *);
|
||||
/* last two arguments are img and mask, from texture lookup */
|
||||
void (*draw)(void *, RenderState *, PyObject *, PyObject *);
|
||||
} RenderModeInterface;
|
||||
};
|
||||
|
||||
/* figures out the render mode to use from the given ChunkRenderer */
|
||||
RenderModeInterface *get_render_mode(RenderState *state);
|
||||
/* python bindings */
|
||||
PyObject *get_render_modes(PyObject *self, PyObject *args);
|
||||
PyObject *get_render_mode_info(PyObject *self, PyObject *args);
|
||||
PyObject *get_render_mode_parent(PyObject *self, PyObject *args);
|
||||
PyObject *get_render_mode_inheritance(PyObject *self, PyObject *args);
|
||||
PyObject *get_render_mode_children(PyObject *self, PyObject *args);
|
||||
|
||||
/* individual rendermode interface declarations follow */
|
||||
|
||||
@@ -79,6 +85,20 @@ typedef struct {
|
||||
} RenderModeNormal;
|
||||
extern RenderModeInterface rendermode_normal;
|
||||
|
||||
/* OVERLAY */
|
||||
typedef struct {
|
||||
/* top facemask and white color image, for drawing overlays */
|
||||
PyObject *facemask_top, *white_color;
|
||||
/* only show overlay on top of solid or fluid blocks */
|
||||
PyObject *solid_blocks, *fluid_blocks;
|
||||
/* can be overridden in derived classes to control
|
||||
overlay alpha and color
|
||||
last four vars are r, g, b, a out */
|
||||
void (*get_color)(void *, RenderState *,
|
||||
unsigned char *, unsigned char *, unsigned char *, unsigned char *);
|
||||
} RenderModeOverlay;
|
||||
extern RenderModeInterface rendermode_overlay;
|
||||
|
||||
/* LIGHTING */
|
||||
typedef struct {
|
||||
/* inherits from normal render mode */
|
||||
@@ -109,14 +129,31 @@ extern RenderModeInterface rendermode_night;
|
||||
|
||||
/* SPAWN */
|
||||
typedef struct {
|
||||
/* inherits from night */
|
||||
RenderModeNight parent;
|
||||
/* inherits from overlay */
|
||||
RenderModeOverlay parent;
|
||||
|
||||
/* used to figure out which blocks are spawnable */
|
||||
PyObject *solid_blocks, *nospawn_blocks, *fluid_blocks;
|
||||
/* replacement for black_color */
|
||||
PyObject *red_color;
|
||||
PyObject *nospawn_blocks;
|
||||
PyObject *skylight, *blocklight;
|
||||
} RenderModeSpawn;
|
||||
extern RenderModeInterface rendermode_spawn;
|
||||
|
||||
/* CAVE */
|
||||
typedef struct {
|
||||
/* render blocks with lighting mode */
|
||||
RenderModeNormal parent;
|
||||
|
||||
/* data used to know where the surface is */
|
||||
PyObject *skylight;
|
||||
PyObject *left_skylight;
|
||||
PyObject *right_skylight;
|
||||
PyObject *up_left_skylight;
|
||||
PyObject *up_right_skylight;
|
||||
|
||||
/* colors used for tinting */
|
||||
PyObject *depth_colors;
|
||||
|
||||
} RenderModeCave;
|
||||
extern RenderModeInterface rendermode_cave;
|
||||
|
||||
#endif /* __RENDERMODES_H_INCLUDED__ */
|
||||
|
||||
@@ -121,14 +121,9 @@ def transform_image(img, blockID=None):
|
||||
|
||||
"""
|
||||
|
||||
if blockID in (81,92): # cacti and cake
|
||||
# Resize to 15x15, since the cactus and the cake textures are a little smaller than the other textures
|
||||
img = img.resize((15, 15), Image.BILINEAR)
|
||||
|
||||
else:
|
||||
# Resize to 17x17, since the diagonal is approximately 24 pixels, a nice
|
||||
# even number that can be split in half twice
|
||||
img = img.resize((17, 17), Image.BILINEAR)
|
||||
img = img.resize((17, 17), Image.ANTIALIAS)
|
||||
|
||||
# Build the Affine transformation matrix for this perspective
|
||||
transform = numpy.matrix(numpy.identity(3))
|
||||
@@ -168,7 +163,7 @@ def transform_image_side(img, blockID=None):
|
||||
img = n
|
||||
|
||||
# Size of the cube side before shear
|
||||
img = img.resize((12,12))
|
||||
img = img.resize((12,12), Image.ANTIALIAS)
|
||||
|
||||
# Apply shear
|
||||
transform = numpy.matrix(numpy.identity(3))
|
||||
@@ -184,7 +179,7 @@ def transform_image_slope(img, blockID=None):
|
||||
in the -y direction (reflect for +x direction). Used for minetracks"""
|
||||
|
||||
# Take the same size as trasform_image_side
|
||||
img = img.resize((12,12))
|
||||
img = img.resize((12,12), Image.ANTIALIAS)
|
||||
|
||||
# Apply shear
|
||||
transform = numpy.matrix(numpy.identity(3))
|
||||
@@ -210,7 +205,6 @@ def _build_block(top, side, blockID=None):
|
||||
return img
|
||||
|
||||
side = transform_image_side(side, blockID)
|
||||
|
||||
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
|
||||
# Darken the sides slightly. These methods also affect the alpha layer,
|
||||
@@ -224,10 +218,7 @@ def _build_block(top, side, blockID=None):
|
||||
otherside.putalpha(othersidealpha)
|
||||
|
||||
## special case for non-block things
|
||||
# TODO once torches are handled by generate_special_texture, remove
|
||||
# them from this list
|
||||
if blockID in (37,38,6,39,40,50,83,75,76): ## flowers, sapling, mushrooms, regular torch, reeds,
|
||||
# redstone torch on, redstone torch off
|
||||
if blockID in (37,38,6,39,40,83,30): ## flowers, sapling, mushrooms, reeds, web
|
||||
#
|
||||
# instead of pasting these blocks at the cube edges, place them in the middle:
|
||||
# and omit the top
|
||||
@@ -237,9 +228,9 @@ def _build_block(top, side, blockID=None):
|
||||
|
||||
|
||||
if blockID in (81,): # cacti!
|
||||
composite.alpha_over(img, side, (2,6), side)
|
||||
composite.alpha_over(img, otherside, (10,6), otherside)
|
||||
composite.alpha_over(img, top, (0,2), top)
|
||||
composite.alpha_over(img, side, (1,6), side)
|
||||
composite.alpha_over(img, otherside, (11,6), otherside)
|
||||
composite.alpha_over(img, top, (0,0), top)
|
||||
elif blockID in (44,): # half step
|
||||
# shift each texture down 6 pixels
|
||||
composite.alpha_over(img, side, (0,12), side)
|
||||
@@ -277,9 +268,37 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None
|
||||
side3 is in the -x (bottom left, north)
|
||||
side4 is in the +y (bottom right, west)
|
||||
|
||||
A non transparent block uses top, side 3 and side 4
|
||||
A non transparent block uses top, side 3 and side 4.
|
||||
|
||||
If top is a tuple then first member is the top image and the second
|
||||
member is an increment (integer) from 0 to 12. This increment will
|
||||
used to crop the side images to look like a block and to paste all
|
||||
the images increment pixels lower. Using increment = 6 will create
|
||||
a half-block.
|
||||
|
||||
NOTE: this method uses the top of the texture image (as done in
|
||||
minecraft with beds)
|
||||
|
||||
"""
|
||||
|
||||
increment = 0
|
||||
if isinstance(top, tuple):
|
||||
increment = top[1]
|
||||
crop_height = int(increment * 16./12.)
|
||||
top = top[0]
|
||||
if side1 != None:
|
||||
side1 = side1.copy()
|
||||
ImageDraw.Draw(side1).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0))
|
||||
if side2 != None:
|
||||
side2 = side2.copy()
|
||||
ImageDraw.Draw(side2).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0))
|
||||
if side3 != None:
|
||||
side3 = side3.copy()
|
||||
ImageDraw.Draw(side3).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0))
|
||||
if side4 != None:
|
||||
side4 = side4.copy()
|
||||
ImageDraw.Draw(side4).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0))
|
||||
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
|
||||
# first back sides
|
||||
@@ -292,7 +311,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None
|
||||
side1 = ImageEnhance.Brightness(side1).enhance(0.9)
|
||||
side1.putalpha(sidealpha)
|
||||
|
||||
composite.alpha_over(img, side1, (0,0), side1)
|
||||
composite.alpha_over(img, side1, (0,0 + increment), side1)
|
||||
|
||||
|
||||
if side2 != None :
|
||||
@@ -303,7 +322,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None
|
||||
side2 = ImageEnhance.Brightness(side2).enhance(0.8)
|
||||
side2.putalpha(sidealpha2)
|
||||
|
||||
composite.alpha_over(img, side2, (12,0), side2)
|
||||
composite.alpha_over(img, side2, (12,0 + increment), side2)
|
||||
|
||||
if bottom != None :
|
||||
bottom = transform_image(bottom, blockID)
|
||||
@@ -318,7 +337,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None
|
||||
side3 = ImageEnhance.Brightness(side3).enhance(0.9)
|
||||
side3.putalpha(sidealpha)
|
||||
|
||||
composite.alpha_over(img, side3, (0,6), side3)
|
||||
composite.alpha_over(img, side3, (0,6 + increment), side3)
|
||||
|
||||
if side4 != None :
|
||||
side4 = transform_image_side(side4, blockID)
|
||||
@@ -329,11 +348,11 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None
|
||||
side4 = ImageEnhance.Brightness(side4).enhance(0.8)
|
||||
side4.putalpha(sidealpha)
|
||||
|
||||
composite.alpha_over(img, side4, (12,6), side4)
|
||||
composite.alpha_over(img, side4, (12,6 + increment), side4)
|
||||
|
||||
if top != None :
|
||||
top = transform_image(top, blockID)
|
||||
composite.alpha_over(img, top, (0,0), top)
|
||||
composite.alpha_over(img, top, (0, increment), top)
|
||||
|
||||
return img
|
||||
|
||||
@@ -348,34 +367,34 @@ def _build_blockimages():
|
||||
# texture array (terrain_images), which comes from terrain.png's cells, left to right top to
|
||||
# bottom.
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||
topids = [ -1, 1, 0, 2, 16, 4, 15, 17,205,205,237,237, 18, 19, 32, 33,
|
||||
topids = [ -1, 1, 0, 2, 16, 4, -1, 17,205,205,237,237, 18, 19, 32, 33,
|
||||
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
||||
34, -1, 52, 48, 49,160,144, -1,176, 74, -1, -1, -1, -1, -1, -1, # Cloths are left out, sandstone (it has top, side, and bottom wich is ignored here), note block
|
||||
34, -1, 52, 48, 49,160,144, -1,176, 74, -1, -1, -1, -1, 11, -1,
|
||||
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
||||
-1, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 8, 35, # Gold/iron blocks? Doublestep? TNT from above?
|
||||
-1, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 9, 4,
|
||||
# 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
||||
36, 37, -1, -1, 65, -1, 25, -1, 98, 24, -1, -1, 86, -1, -1, -1, # Torch from above? leaving out fire. Redstone wire? Crops/furnaces handled elsewhere. sign post
|
||||
36, 37, -1, -1, 65, -1, -1, -1, 50, 24, -1, -1, 86, -1, -1, -1,
|
||||
# 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67, # door,ladder left out. Minecart rail orientation, redstone torches
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67,
|
||||
# 80 81 82 83 84 85 86 87 88 89 90 91
|
||||
66, 69, 72, 73, 74, -1,102,103,104,105,-1, 102 # clay?
|
||||
66, 69, 72, 73, 75, -1,102,103,104,105,-1, 102 # clay?
|
||||
]
|
||||
|
||||
# NOTE: For non-block textures, the sideid is ignored, but can't be -1
|
||||
|
||||
# And side textures of all block types
|
||||
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
||||
sideids = [ -1, 1, 3, 2, 16, 4, 15, 17,205,205,237,237, 18, 19, 32, 33,
|
||||
sideids = [ -1, 1, 3, 2, 16, 4, -1, 17,205,205,237,237, 18, 19, 32, 33,
|
||||
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
||||
34, -1, 52, 48, 49,160,144, -1,192, 74, -1, -1,- 1, -1, -1, -1,
|
||||
34, -1, 52, 48, 49,160,144, -1,192, 74, -1, -1,- 1, -1, 11, -1,
|
||||
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
||||
-1, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 8, 35,
|
||||
# 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
||||
36, 37, -1, -1, 65, -1, 25,101, 98, 24, -1, -1, 86, -1, -1, -1,
|
||||
36, 37, -1, -1, 65, -1, -1,101, 50, 24, -1, -1, 86, -1, -1, -1,
|
||||
# 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67,
|
||||
# 80 81 82 83 84 85 86 87 88 89 90 91
|
||||
66, 69, 72, 73, 74,-1 ,118,103,104,105, -1, 118
|
||||
66, 70, 72, 73, 74,-1 ,118,103,104,105, -1, 118
|
||||
]
|
||||
|
||||
# This maps block id to the texture that goes on the side of the block
|
||||
@@ -433,8 +452,38 @@ def generate_special_texture(blockID, data):
|
||||
# all need to behandled here (and in chunkpy)
|
||||
|
||||
if blockID == 2: # grass
|
||||
top = tintTexture(terrain_images[0],(115,175,71))
|
||||
img = _build_block(top, terrain_images[3], 2)
|
||||
# data & 0x10 means SNOW sides
|
||||
side_img = terrain_images[3]
|
||||
if data & 0x10:
|
||||
side_img = terrain_images[68]
|
||||
img = _build_block(terrain_images[0], side_img, 2)
|
||||
if not data & 0x10:
|
||||
colored = tintTexture(biome_grass_texture, (115, 175, 71))
|
||||
composite.alpha_over(img, colored, (0, 0), colored)
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
|
||||
if blockID == 6: # saplings
|
||||
# The bottom two bits are used fo the sapling type, the top two
|
||||
# bits are used as a grow-counter for the tree.
|
||||
|
||||
if data & 0x3 == 0: # usual saplings
|
||||
toptexture = terrain_images[15]
|
||||
sidetexture = terrain_images[15]
|
||||
|
||||
if data & 0x3 == 1: # spruce sapling
|
||||
toptexture = terrain_images[63]
|
||||
sidetexture = terrain_images[63]
|
||||
|
||||
if data & 0x3 == 2: # birch sapling
|
||||
toptexture = terrain_images[79]
|
||||
sidetexture = terrain_images[79]
|
||||
|
||||
if data & 0x3 == 3: # unused usual sapling
|
||||
toptexture = terrain_images[15]
|
||||
sidetexture = terrain_images[15]
|
||||
|
||||
img = _build_block(toptexture, sidetexture, blockID)
|
||||
return (img.convert("RGB"),img.split()[3])
|
||||
|
||||
|
||||
@@ -489,16 +538,50 @@ def generate_special_texture(blockID, data):
|
||||
img = _build_block(t, t, 18)
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
if blockID == 23: # dispenser
|
||||
top = transform_image(terrain_images[62])
|
||||
side1 = transform_image_side(terrain_images[46])
|
||||
side2 = transform_image_side(terrain_images[45]).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
if blockID == 26: # bed
|
||||
increment = 5
|
||||
left_face = None
|
||||
right_face = None
|
||||
if data & 0x8 == 0x8: # head of the bed
|
||||
top = terrain_images[135]
|
||||
if data & 0x00 == 0x00: # head pointing to West
|
||||
top = top.copy().rotate(270)
|
||||
left_face = terrain_images[151]
|
||||
right_face = terrain_images[152]
|
||||
if data & 0x01 == 0x01: # ... North
|
||||
top = top.rotate(270)
|
||||
left_face = terrain_images[152]
|
||||
right_face = terrain_images[151]
|
||||
if data & 0x02 == 0x02: # East
|
||||
top = top.rotate(180)
|
||||
left_face = terrain_images[151].transpose(Image.FLIP_LEFT_RIGHT)
|
||||
right_face = None
|
||||
if data & 0x03 == 0x03: # South
|
||||
right_face = None
|
||||
right_face = terrain_images[151].transpose(Image.FLIP_LEFT_RIGHT)
|
||||
|
||||
else: # foot of the bed
|
||||
top = terrain_images[134]
|
||||
if data & 0x00 == 0x00: # head pointing to West
|
||||
top = top.rotate(270)
|
||||
left_face = terrain_images[150]
|
||||
right_face = None
|
||||
if data & 0x01 == 0x01: # ... North
|
||||
top = top.rotate(270)
|
||||
left_face = None
|
||||
right_face = terrain_images[150]
|
||||
if data & 0x02 == 0x02: # East
|
||||
top = top.rotate(180)
|
||||
left_face = terrain_images[150].transpose(Image.FLIP_LEFT_RIGHT)
|
||||
right_face = terrain_images[149].transpose(Image.FLIP_LEFT_RIGHT)
|
||||
if data & 0x03 == 0x03: # South
|
||||
left_face = terrain_images[149]
|
||||
right_face = terrain_images[150].transpose(Image.FLIP_LEFT_RIGHT)
|
||||
|
||||
top = (top, increment)
|
||||
img = _build_full_block(top, None, None, left_face, right_face)
|
||||
|
||||
composite.alpha_over(img, side1, (0,6), side1)
|
||||
composite.alpha_over(img, side2, (12,6), side2)
|
||||
composite.alpha_over(img, top, (0,0), top)
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
|
||||
@@ -731,6 +814,48 @@ def generate_special_texture(blockID, data):
|
||||
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
if blockID == 54: # chests
|
||||
# First to bits of the pseudo data store if it's a single chest
|
||||
# or it's a double chest, first half or second half.
|
||||
# The to last bits store the orientation.
|
||||
|
||||
top = terrain_images[25]
|
||||
side = terrain_images[26]
|
||||
|
||||
if data & 12 == 0: # single chest
|
||||
front = terrain_images[27]
|
||||
back = terrain_images[26]
|
||||
|
||||
elif data & 12 == 4: # double, first half
|
||||
front = terrain_images[41]
|
||||
back = terrain_images[57]
|
||||
|
||||
elif data & 12 == 8: # double, second half
|
||||
front = terrain_images[42]
|
||||
back = terrain_images[58]
|
||||
|
||||
else: # just in case
|
||||
front = terrain_images[25]
|
||||
side = terrain_images[25]
|
||||
back = terrain_images[25]
|
||||
|
||||
if data & 3 == 0: # facing west
|
||||
img = _build_full_block(top, None, None, side, front)
|
||||
|
||||
elif data & 3 == 1: # north
|
||||
img = _build_full_block(top, None, None, front, side)
|
||||
|
||||
elif data & 3 == 2: # east
|
||||
img = _build_full_block(top, None, None, side, back)
|
||||
|
||||
elif data & 3 == 3: # south
|
||||
img = _build_full_block(top, None, None, back, side)
|
||||
|
||||
else:
|
||||
img = _build_full_block(top, None, None, back, side)
|
||||
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
|
||||
if blockID == 55: # redstone wire
|
||||
|
||||
@@ -832,29 +957,28 @@ def generate_special_texture(blockID, data):
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
|
||||
if blockID == 61: #furnace
|
||||
top = transform_image(terrain_images[62])
|
||||
side1 = transform_image_side(terrain_images[45])
|
||||
side2 = transform_image_side(terrain_images[44]).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
if blockID in (61, 62, 23): #furnace and burning furnace
|
||||
top = terrain_images[62]
|
||||
side = terrain_images[45]
|
||||
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
if blockID == 61:
|
||||
front = terrain_images[44]
|
||||
|
||||
composite.alpha_over(img, side1, (0,6), side1)
|
||||
composite.alpha_over(img, side2, (12,6), side2)
|
||||
composite.alpha_over(img, top, (0,0), top)
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
elif blockID == 62:
|
||||
front = terrain_images[45+16]
|
||||
|
||||
elif blockID == 23:
|
||||
front = terrain_images[46]
|
||||
|
||||
if blockID == 62: # lit furnace
|
||||
top = transform_image(terrain_images[62])
|
||||
side1 = transform_image_side(terrain_images[45])
|
||||
side2 = transform_image_side(terrain_images[45+16]).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
if data == 3: # pointing west
|
||||
img = _build_full_block(top, None, None, side, front)
|
||||
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
elif data == 4: # pointing north
|
||||
img = _build_full_block(top, None, None, front, side)
|
||||
|
||||
else: # in any other direction the front can't be seen
|
||||
img = _build_full_block(top, None, None, side, side)
|
||||
|
||||
composite.alpha_over(img, side1, (0,6), side1)
|
||||
composite.alpha_over(img, side2, (12,6), side2)
|
||||
composite.alpha_over(img, top, (0,0), top)
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
|
||||
@@ -938,8 +1062,24 @@ def generate_special_texture(blockID, data):
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
|
||||
if blockID == 66: # minetrack:
|
||||
if blockID in (27, 28, 66): # minetrack:
|
||||
|
||||
if blockID == 27: # powered rail
|
||||
if data & 0x8 == 0: # unpowered
|
||||
raw_straight = terrain_images[163]
|
||||
raw_corner = terrain_images[112] # they don't exist but make the code
|
||||
# much simplier
|
||||
elif data & 0x8 == 0x8: # powered
|
||||
raw_straight = terrain_images[179]
|
||||
raw_corner = terrain_images[112] # leave corners for code simplicity
|
||||
# filter the 'powered' bit
|
||||
data = data & 0x7
|
||||
|
||||
elif blockID == 28: # detector rail
|
||||
raw_straight = terrain_images[195]
|
||||
raw_corner = terrain_images[112] # leave corners for code simplicity
|
||||
|
||||
elif blockID == 66: # normal rail
|
||||
raw_straight = terrain_images[128]
|
||||
raw_corner = terrain_images[112]
|
||||
|
||||
@@ -1034,9 +1174,9 @@ def generate_special_texture(blockID, data):
|
||||
|
||||
# Compose the fence big stick
|
||||
fence_big = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
composite.alpha_over(fence_big,fence_side, (4,4),fence_side)
|
||||
composite.alpha_over(fence_big,fence_other_side, (8,4),fence_other_side)
|
||||
composite.alpha_over(fence_big,fence_top, (-1,1),fence_top)
|
||||
composite.alpha_over(fence_big,fence_side, (5,4),fence_side)
|
||||
composite.alpha_over(fence_big,fence_other_side, (7,4),fence_other_side)
|
||||
composite.alpha_over(fence_big,fence_top, (0,1),fence_top)
|
||||
|
||||
# Now render the small sticks.
|
||||
# Create needed images
|
||||
@@ -1098,16 +1238,36 @@ def generate_special_texture(blockID, data):
|
||||
|
||||
|
||||
if blockID in (86,91): # pumpkins, jack-o-lantern
|
||||
top = transform_image(terrain_images[102])
|
||||
top = terrain_images[102]
|
||||
frontID = 119 if blockID == 86 else 120
|
||||
side1 = transform_image_side(terrain_images[frontID])
|
||||
side2 = transform_image_side(terrain_images[118]).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
front = terrain_images[frontID]
|
||||
side = terrain_images[118]
|
||||
|
||||
if data == 0: # pointing west
|
||||
img = _build_full_block(top, None, None, side, front)
|
||||
|
||||
elif data == 1: # pointing north
|
||||
img = _build_full_block(top, None, None, front, side)
|
||||
|
||||
else: # in any other direction the front can't be seen
|
||||
img = _build_full_block(top, None, None, side, side)
|
||||
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
|
||||
if blockID == 90: # portal
|
||||
portaltexture = _load_image("portal.png")
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
|
||||
composite.alpha_over(img, side1, (0,6), side1)
|
||||
composite.alpha_over(img, side2, (12,6), side2)
|
||||
composite.alpha_over(img, top, (0,0), top)
|
||||
side = transform_image_side(portaltexture)
|
||||
otherside = side.transpose(Image.FLIP_TOP_BOTTOM)
|
||||
|
||||
if data in (1,4):
|
||||
composite.alpha_over(img, side, (5,4), side)
|
||||
|
||||
if data in (2,8):
|
||||
composite.alpha_over(img, otherside, (5,4), otherside)
|
||||
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
|
||||
@@ -1128,13 +1288,129 @@ def generate_special_texture(blockID, data):
|
||||
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
|
||||
composite.alpha_over(img, side, (2,12), side)
|
||||
composite.alpha_over(img, otherside, (10,12), otherside)
|
||||
composite.alpha_over(img, top, (0,8), top)
|
||||
composite.alpha_over(img, side, (1,12), side)
|
||||
composite.alpha_over(img, otherside, (11,13), otherside) # workaround, fixes a hole
|
||||
composite.alpha_over(img, otherside, (12,12), otherside)
|
||||
composite.alpha_over(img, top, (0,6), top)
|
||||
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
|
||||
if blockID in (93, 94): # redstone repeaters, ON and OFF
|
||||
# NOTE: this function uses the redstone torches generated above,
|
||||
# this must run after the function of the torches.
|
||||
|
||||
top = terrain_images[131] if blockID == 93 else terrain_images[147]
|
||||
side = terrain_images[5]
|
||||
increment = 9
|
||||
|
||||
if (data & 0x3) == 0: # pointing east
|
||||
pass
|
||||
|
||||
if (data & 0x3) == 1: # pointing south
|
||||
top = top.rotate(270)
|
||||
|
||||
if (data & 0x3) == 2: # pointing west
|
||||
top = top.rotate(180)
|
||||
|
||||
if (data & 0x3) == 3: # pointing north
|
||||
top = top.rotate(90)
|
||||
|
||||
img = _build_full_block( (top, increment), None, None, side, side)
|
||||
|
||||
# paste redstone torches everywhere!
|
||||
t = specialblockmap[(75,5)] if blockID == 93 else specialblockmap[(76,5)]
|
||||
torch = t[0].copy() # textures are stored as tuples (RGB,A)
|
||||
torch.putalpha(t[1])
|
||||
|
||||
# the torch is too tall for the repeater, crop the bottom.
|
||||
ImageDraw.Draw(torch).rectangle((0,16,24,24),outline=(0,0,0,0),fill=(0,0,0,0))
|
||||
|
||||
# touch up the 3d effect with big rectangles, just in case, for other texture packs
|
||||
ImageDraw.Draw(torch).rectangle((0,24,10,15),outline=(0,0,0,0),fill=(0,0,0,0))
|
||||
ImageDraw.Draw(torch).rectangle((12,15,24,24),outline=(0,0,0,0),fill=(0,0,0,0))
|
||||
|
||||
# torch positions for every redstone torch orientation.
|
||||
#
|
||||
# This is a horrible list of torch orientations. I tried to
|
||||
# obtain these orientations by rotating the positions for one
|
||||
# orientation, but pixel rounding is horrible and messes the
|
||||
# torches.
|
||||
|
||||
if (data & 0x3) == 0: # pointing east
|
||||
if (data & 0xC) == 0: # one tick delay
|
||||
moving_torch = (1,1)
|
||||
static_torch = (-3,-1)
|
||||
|
||||
elif (data & 0xC) == 4: # two ticks delay
|
||||
moving_torch = (2,2)
|
||||
static_torch = (-3,-1)
|
||||
|
||||
elif (data & 0xC) == 8: # three ticks delay
|
||||
moving_torch = (3,2)
|
||||
static_torch = (-3,-1)
|
||||
|
||||
elif (data & 0xC) == 12: # four ticks delay
|
||||
moving_torch = (4,3)
|
||||
static_torch = (-3,-1)
|
||||
|
||||
elif (data & 0x3) == 1: # pointing south
|
||||
if (data & 0xC) == 0: # one tick delay
|
||||
moving_torch = (1,1)
|
||||
static_torch = (5,-1)
|
||||
|
||||
elif (data & 0xC) == 4: # two ticks delay
|
||||
moving_torch = (2,0)
|
||||
static_torch = (5,-1)
|
||||
|
||||
elif (data & 0xC) == 8: # three ticks delay
|
||||
moving_torch = (3,0)
|
||||
static_torch = (5,-1)
|
||||
|
||||
elif (data & 0xC) == 12: # four ticks delay
|
||||
moving_torch = (4,-1)
|
||||
static_torch = (5,-1)
|
||||
|
||||
elif (data & 0x3) == 2: # pointing west
|
||||
if (data & 0xC) == 0: # one tick delay
|
||||
moving_torch = (1,1)
|
||||
static_torch = (5,3)
|
||||
|
||||
elif (data & 0xC) == 4: # two ticks delay
|
||||
moving_torch = (0,0)
|
||||
static_torch = (5,3)
|
||||
|
||||
elif (data & 0xC) == 8: # three ticks delay
|
||||
moving_torch = (-1,0)
|
||||
static_torch = (5,3)
|
||||
|
||||
elif (data & 0xC) == 12: # four ticks delay
|
||||
moving_torch = (-2,-1)
|
||||
static_torch = (5,3)
|
||||
|
||||
elif (data & 0x3) == 3: # pointing north
|
||||
if (data & 0xC) == 0: # one tick delay
|
||||
moving_torch = (1,1)
|
||||
static_torch = (-3,3)
|
||||
|
||||
elif (data & 0xC) == 4: # two ticks delay
|
||||
moving_torch = (2,0)
|
||||
static_torch = (-3,3)
|
||||
|
||||
elif (data & 0xC) == 8: # three ticks delay
|
||||
moving_torch = (3,0)
|
||||
static_torch = (-3,3)
|
||||
|
||||
elif (data & 0xC) == 12: # four ticks delay
|
||||
moving_torch = (4,-1)
|
||||
static_torch = (-3,3)
|
||||
|
||||
# this paste order it's ok for east and south orientation
|
||||
# but it's wrong for north and west orientations. But using the
|
||||
# default texture pack the torches are small enough to no overlap.
|
||||
composite.alpha_over(img, torch, static_torch, torch)
|
||||
composite.alpha_over(img, torch, moving_torch, torch)
|
||||
|
||||
#~ composite.alpha_over(img, side, (2,6), side)
|
||||
#~ composite.alpha_over(img, otherside, (10,6), otherside)
|
||||
#~ composite.alpha_over(img, top, (0,2), top)
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
|
||||
@@ -1147,7 +1423,7 @@ def tintTexture(im, c):
|
||||
return i
|
||||
|
||||
# generate biome (still grayscale) leaf, grass textures
|
||||
biome_grass_texture = _build_block(terrain_images[0], terrain_images[3], 2)
|
||||
biome_grass_texture = _build_block(terrain_images[0], terrain_images[38], 2)
|
||||
biome_leaf_texture = _build_block(terrain_images[52], terrain_images[52], 18)
|
||||
|
||||
|
||||
@@ -1215,28 +1491,34 @@ def getBiomeData(worlddir, chunkX, chunkY):
|
||||
# (when adding new blocks here and in generate_special_textures,
|
||||
# please, if possible, keep the ascending order of blockid value)
|
||||
|
||||
special_blocks = set([2, 9, 17, 18, 23, 35, 43, 44, 50, 51, 53, 55, 58, 59, \
|
||||
61, 62, 64, 65, 66, 67, 71, 75, 76, 85, 86, 91, 92])
|
||||
special_blocks = set([ 2, 6, 9, 17, 18, 26, 23, 27, 28, 35, 43, 44, 50,
|
||||
51, 53, 54, 55, 58, 59, 61, 62, 64, 65, 66, 67, 71,
|
||||
75, 76, 85, 86, 90, 91, 92, 93, 94])
|
||||
|
||||
# this is a map of special blockIDs to a list of all
|
||||
# possible values for ancillary data that it might have.
|
||||
|
||||
special_map = {}
|
||||
|
||||
special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values.
|
||||
special_map[6] = range(16) # saplings: usual, spruce, birch and future ones (rendered as usual saplings)
|
||||
special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values, uses pseudo data
|
||||
special_map[17] = range(4) # wood: normal, birch and pine
|
||||
special_map[26] = range(12) # bed, orientation
|
||||
special_map[23] = range(6) # dispensers, orientation
|
||||
special_map[27] = range(14) # powered rail, orientation/slope and powered/unpowered
|
||||
special_map[28] = range(6) # detector rail, orientation/slope
|
||||
special_map[35] = range(16) # wool, colored and white
|
||||
special_map[43] = range(4) # stone, sandstone, wooden and cobblestone double-slab
|
||||
special_map[44] = range(4) # stone, sandstone, wooden and cobblestone slab
|
||||
special_map[50] = (1,2,3,4,5) # torch, position in the block
|
||||
special_map[51] = range(16) # fire, position in the block (not implemented)
|
||||
special_map[53] = range(4) # wooden stairs, orientation
|
||||
special_map[55] = range(128) # redstone wire, all the possible combinations
|
||||
special_map[54] = range(12) # chests, orientation and type (single or double), uses pseudo data
|
||||
special_map[55] = range(128) # redstone wire, all the possible combinations, uses pseudo data
|
||||
special_map[58] = (0,) # crafting table
|
||||
special_map[59] = range(8) # crops, grow from 0 to 7
|
||||
special_map[61] = range(6) # furnace, orientation (not implemented)
|
||||
special_map[62] = range(6) # burning furnace, orientation (not implemented)
|
||||
special_map[61] = range(6) # furnace, orientation
|
||||
special_map[62] = range(6) # burning furnace, orientation
|
||||
special_map[64] = range(16) # wooden door, open/close and orientation
|
||||
special_map[65] = (2,3,4,5) # ladder, orientation
|
||||
special_map[66] = range(10) # minecrart tracks, orientation, slope
|
||||
@@ -1244,18 +1526,22 @@ special_map[67] = range(4) # cobblestone stairs, orientation
|
||||
special_map[71] = range(16) # iron door, open/close and orientation
|
||||
special_map[75] = (1,2,3,4,5) # off redstone torch, orientation
|
||||
special_map[76] = (1,2,3,4,5) # on redstone torch, orientation
|
||||
special_map[85] = range(17) # fences, all the possible combination
|
||||
special_map[86] = range(5) # pumpkin, orientation (not implemented)
|
||||
special_map[91] = range(5) # jack-o-lantern, orientation (not implemented)
|
||||
special_map[85] = range(17) # fences, all the possible combination, uses pseudo data
|
||||
special_map[86] = range(5) # pumpkin, orientation
|
||||
special_map[90] = (1,2,4,8) # portal, in 2 orientations, 4 cases, uses pseudo data
|
||||
special_map[91] = range(5) # jack-o-lantern, orientation
|
||||
special_map[92] = range(6) # cake!
|
||||
special_map[93] = range(16) # OFF redstone repeater, orientation and delay (delay not implemented)
|
||||
special_map[94] = range(16) # ON redstone repeater, orientation and delay (delay not implemented)
|
||||
|
||||
# grass and leaves are graysacle in terrain.png
|
||||
# we treat them as special so we can manually tint them
|
||||
# it is unknown how the specific tint (biomes) is calculated
|
||||
special_map[2] = range(11) # grass, grass has not ancildata but is used
|
||||
# in the mod WildGrass, and this small fix
|
||||
# shows the map as expected, and is harmless
|
||||
# for normal maps
|
||||
# also, 0x10 means SNOW sides
|
||||
special_map[2] = range(11) + [0x10,] # grass, grass has not ancildata but is
|
||||
# used in the mod WildGrass, and this
|
||||
# small fix shows the map as expected,
|
||||
# and is harmless for normal maps
|
||||
special_map[18] = range(16) # leaves, birch, normal or pine leaves (not implemented)
|
||||
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ def findGitVersion():
|
||||
with open(os.path.join(this_dir,".git","HEAD")) as f:
|
||||
data = f.read().strip()
|
||||
if data.startswith("ref: "):
|
||||
if not os.path.exists(os.path.join(this_dir,data[5:])):
|
||||
if not os.path.exists(os.path.join(this_dir, ".git", data[5:])):
|
||||
return data
|
||||
with open(os.path.join(this_dir, ".git", data[5:])) as g:
|
||||
return g.read().strip()
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import functools
|
||||
import os
|
||||
import os.path
|
||||
from glob import glob
|
||||
import multiprocessing
|
||||
import Queue
|
||||
import sys
|
||||
@@ -287,10 +288,8 @@ class World(object):
|
||||
p = f.split(".")
|
||||
yield (int(p[1]), int(p[2]), join(self.worlddir, 'region', f))
|
||||
else:
|
||||
for dirpath, dirnames, filenames in os.walk(os.path.join(self.worlddir, 'region')):
|
||||
if not dirnames and filenames and "DIM-1" not in dirpath:
|
||||
for f in filenames:
|
||||
if f.startswith("r.") and f.endswith(".mcr"):
|
||||
for path in glob(os.path.join(self.worlddir, 'region') + "/r.*.*.mcr"):
|
||||
dirpath, f = os.path.split(path)
|
||||
p = f.split(".")
|
||||
yield (int(p[1]), int(p[2]), join(dirpath, f))
|
||||
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
# Please see the README or https://github.com/brownan/Minecraft-Overviewer/wiki/DTT-Upgrade-Guide
|
||||
# for more details.
|
||||
|
||||
# To use this file, simply copy it to settings.py and make any necessary changes
|
||||
# to suite your needs.
|
||||
# This file is not meant to be used directly, but instead it is supposed to
|
||||
# provide examples of interesting things you can do with the settings file. Most
|
||||
# of the time, a simple 'setting_name = value' will work.
|
||||
|
||||
# This file is a python script, so you can import and python module you wish or
|
||||
# use any built-in python function, though this is not normally necessary
|
||||
|
||||
12
setup.py
12
setup.py
@@ -21,6 +21,7 @@ except ImportError:
|
||||
setup_kwargs = {}
|
||||
setup_kwargs['ext_modules'] = []
|
||||
setup_kwargs['cmdclass'] = {}
|
||||
setup_kwargs['options'] = {}
|
||||
|
||||
#
|
||||
# metadata
|
||||
@@ -41,7 +42,6 @@ if py2exe is not None:
|
||||
b = 3
|
||||
else:
|
||||
b = 1
|
||||
setup_kwargs['options'] = {}
|
||||
setup_kwargs['options']['py2exe'] = {'bundle_files' : b, 'excludes': 'Tkinter'}
|
||||
|
||||
#
|
||||
@@ -51,7 +51,7 @@ if py2exe is not None:
|
||||
setup_kwargs['packages'] = ['overviewer_core']
|
||||
setup_kwargs['scripts'] = ['overviewer.py']
|
||||
setup_kwargs['package_data'] = {'overviewer_core':
|
||||
['data/config.js',
|
||||
['data/overviewerConfig.js',
|
||||
'data/textures/*',
|
||||
'data/web_assets/*']}
|
||||
|
||||
@@ -73,8 +73,11 @@ try:
|
||||
except:
|
||||
pil_include = []
|
||||
|
||||
c_overviewer_files = ['main.c', 'composite.c', 'iterate.c', 'endian.c']
|
||||
c_overviewer_files += ['rendermodes.c', 'rendermode-normal.c', 'rendermode-lighting.c', 'rendermode-night.c', 'rendermode-spawn.c']
|
||||
# used to figure out what files to compile
|
||||
render_modes = ['normal', 'overlay', 'lighting', 'night', 'spawn', 'cave']
|
||||
|
||||
c_overviewer_files = ['main.c', 'composite.c', 'iterate.c', 'endian.c', 'rendermodes.c']
|
||||
c_overviewer_files += map(lambda mode: 'rendermode-%s.c' % (mode,), render_modes)
|
||||
c_overviewer_files += ['Draw.c']
|
||||
c_overviewer_includes = ['overviewer.h', 'rendermodes.h']
|
||||
|
||||
@@ -83,6 +86,7 @@ c_overviewer_includes = map(lambda s: 'overviewer_core/src/'+s, c_overviewer_inc
|
||||
|
||||
setup_kwargs['ext_modules'].append(Extension('overviewer_core.c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include] + pil_include, depends=c_overviewer_includes, extra_link_args=[]))
|
||||
|
||||
|
||||
# tell build_ext to build the extension in-place
|
||||
# (NOT in build/)
|
||||
setup_kwargs['options']['build_ext'] = {'inplace' : 1}
|
||||
|
||||
Reference in New Issue
Block a user