0

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:
Aaron Griffith
2011-05-10 20:19:10 -04:00
32 changed files with 2373 additions and 859 deletions

53
CONTRIBUTORS.rst Normal file
View 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>

View File

@@ -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
-------------------

View File

@@ -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()

View File

@@ -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:

View File

@@ -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):

View File

@@ -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};

View 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}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

View File

@@ -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;
};

View File

@@ -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>

View File

@@ -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;

View 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);
}
}
}
};

View File

@@ -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(']);')

View File

@@ -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:

View File

@@ -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()

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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 */
};

View File

@@ -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 */

View 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,
};

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View 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,
};

View File

@@ -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,

View File

@@ -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;
}

View File

@@ -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__ */

View File

@@ -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)

View File

@@ -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()

View File

@@ -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))

View File

@@ -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

View File

@@ -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}