diff --git a/README.rst b/README.rst index 942e4cb..90282f5 100644 --- a/README.rst +++ b/README.rst @@ -195,6 +195,10 @@ Options --list-rendermodes List the available render modes, and a short description of each. +--north-direction=NORTH_DIRECTION + Specifies which corner of the screen north will point to. + Valid options are: lower-left, upper-left, upper-right, lower-right. + --settings=PATH Use this option to load settings from a file. The format of this file is given below. @@ -264,6 +268,10 @@ textures_path source. Overviewer looks in here for terrain.png and other textures before it looks anywhere else. +north_direction + Specifies which corner of the screen north will point to. + Valid options are: lower-left, upper-left, upper-right, lower-right. + Viewing the Results ------------------- Within the output directory you will find two things: an index.html file, and a diff --git a/overviewer.py b/overviewer.py index cbd3f37..3a191b5 100755 --- a/overviewer.py +++ b/overviewer.py @@ -96,6 +96,7 @@ def main(): cpus = 1 avail_rendermodes = c_overviewer.get_render_modes() + avail_north_dirs = ['lower-left', 'upper-left', 'upper-right', 'lower-right'] parser = ConfigOptionParser(usage=helptext, config="settings.py") parser.add_option("-V", "--version", dest="version", helptext="Displays version information and then exits", action="store_true") @@ -116,6 +117,7 @@ def main(): parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, helptext="Print more output. You can specify this option multiple times.") parser.add_option("--skip-js", dest="skipjs", action="store_true", helptext="Don't output marker.js or regions.js") parser.add_option("--no-signs", dest="nosigns", action="store_true", helptext="Don't output signs to markers.js") + parser.add_option("--north-direction", dest="north_direction", helptext="Specifies which corner of the screen north will point to. Valid options are: " + ", ".join(avail_north_dirs) + ".", type="choice", default=avail_north_dirs[0], choices=avail_north_dirs) parser.add_option("--display-config", dest="display_config", action="store_true", helptext="Display the configuration parameters, but don't render the map. Requires all required options to be specified", commandLineOnly=True) #parser.add_option("--write-config", dest="write_config", action="store_true", helptext="Writes out a sample config file", commandLineOnly=True) @@ -219,6 +221,11 @@ def main(): optimizeimages.check_programs(optimizeimg) else: optimizeimg = None + + if options.north_direction: + north_direction = options.north_direction + else: + north_direction = 'lower-left' logging.getLogger().setLevel( logging.getLogger().level + 10*options.quiet) @@ -233,7 +240,7 @@ def main(): logging.info("Notice: Not using biome data for tinting") # First do world-level preprocessing - w = world.World(worlddir, destdir, useBiomeData=useBiomeData, regionlist=regionlist) + w = world.World(worlddir, destdir, useBiomeData=useBiomeData, regionlist=regionlist, north_direction=north_direction) w.go(options.procs) logging.info("Rending the following tilesets: %s", ",".join(options.rendermode)) diff --git a/overviewer_core/chunk.py b/overviewer_core/chunk.py index b885d51..9dd38f2 100644 --- a/overviewer_core/chunk.py +++ b/overviewer_core/chunk.py @@ -73,18 +73,30 @@ def get_lvldata(world, filename, x, y, retries=2): def get_blockarray(level): """Takes the level struct as returned from get_lvldata, and returns the Block array, which just contains all the block ids""" - return numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)) + return level['Blocks'] -def get_blockarray_fromfile(filename): +def get_blockarray_fromfile(filename, north_direction='lower-left'): """Same as get_blockarray except takes a filename. This is a shortcut""" - d = nbt.load_from_region(filename, x, y) + d = nbt.load_from_region(filename, x, y, north_direction) level = d[1]['Level'] - return get_blockarray(level) + chunk_data = level + rots = 0 + if self.north_direction == 'upper-left': + rots = 1 + elif self.north_direction == 'upper-right': + rots = 2 + elif self.north_direction == 'lower-right': + rots = 3 + + chunk_data['Blocks'] = numpy.rot90(numpy.frombuffer( + level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)), + rots) + return get_blockarray(chunk_data) def get_skylight_array(level): """Returns the skylight array. This is 4 bits per block, but it is expanded for you so you may index it normally.""" - skylight = numpy.frombuffer(level['SkyLight'], dtype=numpy.uint8).reshape((16,16,64)) + skylight = level['SkyLight'] # this array is 2 blocks per byte, so expand it skylight_expanded = numpy.empty((16,16,128), dtype=numpy.uint8) # Even elements get the lower 4 bits @@ -97,7 +109,7 @@ def get_blocklight_array(level): """Returns the blocklight array. This is 4 bits per block, but it is expanded for you so you may index it normally.""" # expand just like get_skylight_array() - blocklight = numpy.frombuffer(level['BlockLight'], dtype=numpy.uint8).reshape((16,16,64)) + blocklight = level['BlockLight'] blocklight_expanded = numpy.empty((16,16,128), dtype=numpy.uint8) blocklight_expanded[:,:,::2] = blocklight & 0x0F blocklight_expanded[:,:,1::2] = (blocklight & 0xF0) >> 4 @@ -106,7 +118,7 @@ def get_blocklight_array(level): def get_blockdata_array(level): """Returns the ancillary data from the 'Data' byte array. Data is packed in a similar manner to skylight data""" - return numpy.frombuffer(level['Data'], dtype=numpy.uint8).reshape((16,16,64)) + return level['Data'] def get_tileentity_data(level): """Returns the TileEntities TAG_List from chunk dat file""" diff --git a/overviewer_core/data/web_assets/compass.png b/overviewer_core/data/web_assets/compass_lower-left.png similarity index 100% rename from overviewer_core/data/web_assets/compass.png rename to overviewer_core/data/web_assets/compass_lower-left.png diff --git a/overviewer_core/data/web_assets/compass_lower-right.png b/overviewer_core/data/web_assets/compass_lower-right.png new file mode 100644 index 0000000..8990e7e Binary files /dev/null and b/overviewer_core/data/web_assets/compass_lower-right.png differ diff --git a/overviewer_core/data/web_assets/compass_upper-left.png b/overviewer_core/data/web_assets/compass_upper-left.png new file mode 100644 index 0000000..60e12d7 Binary files /dev/null and b/overviewer_core/data/web_assets/compass_upper-left.png differ diff --git a/overviewer_core/data/web_assets/compass_upper-right.png b/overviewer_core/data/web_assets/compass_upper-right.png new file mode 100644 index 0000000..6291365 Binary files /dev/null and b/overviewer_core/data/web_assets/compass_upper-right.png differ diff --git a/overviewer_core/data/web_assets/overviewer.js b/overviewer_core/data/web_assets/overviewer.js index 12ca796..cdf9339 100644 --- a/overviewer_core/data/web_assets/overviewer.js +++ b/overviewer_core/data/web_assets/overviewer.js @@ -536,6 +536,19 @@ var overviewer = { var perPixel = 1.0 / (overviewerConfig.CONST.tileSize * Math.pow(2, overviewerConfig.map.zoomLevels)); + if(overviewerConfig.map.north_direction == 'upper-left'){ + temp = x; + x = -y-1; + y = temp; + } else if(overviewerConfig.map.north_direction == 'upper-right'){ + x = -x-1; + y = -y-1; + } else if(overviewerConfig.map.north_direction == 'lower-right'){ + temp = x; + x = y; + y = -temp-1; + } + // This information about where the center column is may change with // a different drawing implementation -- check it again after any // drawing overhauls! @@ -607,6 +620,19 @@ var overviewer = { point.x += 64; point.z -= 64; + if(overviewerConfig.map.north_direction == 'upper-left'){ + temp = point.z; + point.z = -point.x; + point.x = temp; + } else if(overviewerConfig.map.north_direction == 'upper-right'){ + point.x = -point.x; + point.z = -point.z; + } else if(overviewerConfig.map.north_direction == 'lower-right'){ + temp = point.z; + point.z = point.x; + point.x = -temp; + } + return point; }, /** diff --git a/overviewer_core/data/web_assets/overviewerConfig.js b/overviewer_core/data/web_assets/overviewerConfig.js index bdb6015..c99398e 100644 --- a/overviewer_core/data/web_assets/overviewerConfig.js +++ b/overviewer_core/data/web_assets/overviewerConfig.js @@ -14,7 +14,7 @@ var overviewerConfig = { 'image': { 'defaultMarker': 'signpost.png', 'signMarker': 'signpost_icon.png', - 'compass': 'compass.png', + 'compass': 'compass_{north_direction}.png', 'spawnMarker': 'http://google-maps-icons.googlecode.com/files/home.png', 'queryMarker': 'http://google-maps-icons.googlecode.com/files/regroup.png' }, @@ -94,7 +94,11 @@ var overviewerConfig = { * 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 + 'debug': false, + /** + * Set which way north points. + */ + 'north_direction': '{north_direction}' }, /** * Group definitions for objects that are partially selectable (signs and diff --git a/overviewer_core/googlemap.py b/overviewer_core/googlemap.py index a4fb156..82b4869 100644 --- a/overviewer_core/googlemap.py +++ b/overviewer_core/googlemap.py @@ -77,6 +77,7 @@ class MapGen(object): self.web_assets_hook = configInfo.get('web_assets_hook', None) self.web_assets_path = configInfo.get('web_assets_path', None) self.bg_color = configInfo.get('bg_color') + self.north_direction = configInfo.get('north_direction', 'lower-left') if not len(quadtrees) > 0: raise ValueError("there must be at least one quadtree to work on") @@ -121,6 +122,8 @@ class MapGen(object): "{maxzoom}", str(zoomlevel)) config = config.replace( "{zoomlevels}", str(zoomlevel)) + config = config.replace( + "{north_direction}", self.north_direction) config = config.replace("{spawn_coords}", json.dumps(list(self.world.spawn))) diff --git a/overviewer_core/nbt.py b/overviewer_core/nbt.py index aa30700..895eca9 100644 --- a/overviewer_core/nbt.py +++ b/overviewer_core/nbt.py @@ -17,6 +17,7 @@ import gzip, zlib import struct import StringIO import os +import numpy # decorator to handle filename or object as first parameter def _file_loader(func): @@ -34,15 +35,15 @@ def _file_loader(func): def load(fileobj): return NBTFileReader(fileobj).read_all() -def load_from_region(filename, x, y): - nbt = load_region(filename).load_chunk(x, y) +def load_from_region(filename, x, y, north_direction): + nbt = load_region(filename, north_direction).load_chunk(x, y) if nbt is None: return None ## return none. I think this is who we should indicate missing chunks #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) return nbt.read_all() -def load_region(filename): - return MCRFileReader(filename) +def load_region(filename, north_direction): + return MCRFileReader(filename, north_direction) # compile the unpacker's into a classes @@ -199,13 +200,24 @@ class MCRFileReader(object): chunks (as instances of NBTFileReader), getting chunk timestamps, and for listing chunks contained in the file.""" - def __init__(self, filename): + def __init__(self, filename, north_direction): self._file = None self._filename = filename + self.north_direction = north_direction # cache used when the entire header tables are read in get_chunks() self._locations = None self._timestamps = None self._chunks = None + + def get_north_rotations(self): + if self.north_direction == 'upper-left': + return 3 + elif self.north_direction == 'upper-right': + return 2 + elif self.north_direction == 'lower-right': + return 1 + elif self.north_direction == 'lower-left': + return 0 def _read_24bit_int(self): """Read in a 24-bit, big-endian int, used in the chunk @@ -318,21 +330,24 @@ class MCRFileReader(object): self.openfile() self._chunks = None - self._locations = [] + self._locations = [0]*32*32 self._timestamps = [] # go to the beginning of the file self._file.seek(0) # read chunk location table - locations_append = self._locations.append - for _ in xrange(32*32): - locations_append(self._read_chunk_location()) + locations_index = numpy.reshape(numpy.rot90(numpy.reshape(range(32*32), + (32, 32)), -self.get_north_rotations()), -1) + for i in locations_index: + self._locations[i] = self._read_chunk_location() # read chunk timestamp table timestamp_append = self._timestamps.append for _ in xrange(32*32): timestamp_append(self._read_chunk_timestamp()) + self._timestamps = numpy.reshape(numpy.rot90(numpy.reshape( + self._timestamps, (32,32)),self.get_north_rotations()), -1) if closeFile: self.closefile() diff --git a/overviewer_core/rendernode.py b/overviewer_core/rendernode.py index 054e2ea..016e287 100644 --- a/overviewer_core/rendernode.py +++ b/overviewer_core/rendernode.py @@ -65,7 +65,8 @@ def pool_initializer(rendernode): # make sure textures are generated for this process # and initialize c_overviewer - textures.generate(path=rendernode.options.get('textures_path', None)) + textures.generate(path=rendernode.options.get('textures_path', None), + north_direction=rendernode.options.get('north_direction', None)) c_overviewer.init_chunk_render() # load biome data in each process, if needed diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index c6ad4a1..dc5fd69 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -513,6 +513,9 @@ def generate_texture_tuple(img, blockid): def generate_special_texture(blockID, data): """Generates a special texture, such as a correctly facing minecraft track""" + + data = convert_data(blockID, data) + # blocks need to be handled here (and in chunk.py) if blockID == 2: # grass @@ -1679,6 +1682,237 @@ def generate_special_texture(blockID, data): return None +def convert_data(blockID, data): + if blockID == 26: # bed + #Masked to not clobber block head/foot info + if _north == 'upper-left': + if (data & 0b0011) == 0: data = data & 0b1100 | 1 + elif (data & 0b0011) == 1: data = data & 0b1100 | 2 + elif (data & 0b0011) == 2: data = data & 0b1100 | 3 + elif (data & 0b0011) == 3: data = data & 0b1100 | 0 + elif _north == 'upper-right': + if (data & 0b0011) == 0: data = data & 0b1100 | 2 + elif (data & 0b0011) == 1: data = data & 0b1100 | 3 + elif (data & 0b0011) == 2: data = data & 0b1100 | 0 + elif (data & 0b0011) == 3: data = data & 0b1100 | 1 + elif _north == 'lower-right': + if (data & 0b0011) == 0: data = data & 0b1100 | 3 + elif (data & 0b0011) == 1: data = data & 0b1100 | 0 + elif (data & 0b0011) == 2: data = data & 0b1100 | 1 + elif (data & 0b0011) == 3: data = data & 0b1100 | 2 + if blockID in (29, 33, 34): # sticky piston, piston, piston extension + #Masked to not clobber block head/foot info + if _north == 'upper-left': + if (data & 0b0111) == 2: data = data & 0b1000 | 5 + elif (data & 0b0111) == 3: data = data & 0b1000 | 4 + elif (data & 0b0111) == 4: data = data & 0b1000 | 2 + elif (data & 0b0111) == 5: data = data & 0b1000 | 3 + elif _north == 'upper-right': + if (data & 0b0111) == 2: data = data & 0b1000 | 3 + elif (data & 0b0111) == 3: data = data & 0b1000 | 2 + elif (data & 0b0111) == 4: data = data & 0b1000 | 5 + elif (data & 0b0111) == 5: data = data & 0b1000 | 4 + elif _north == 'lower-right': + if (data & 0b0111) == 2: data = data & 0b1000 | 4 + elif (data & 0b0111) == 3: data = data & 0b1000 | 5 + elif (data & 0b0111) == 4: data = data & 0b1000 | 3 + elif (data & 0b0111) == 5: data = data & 0b1000 | 2 + if blockID in (27, 28, 66): # minetrack: + #Masked to not clobber powered rail on/off info + #Ascending and flat straight + if _north == 'upper-left': + if (data & 0b0111) == 0: data = data & 0b1000 | 1 + elif (data & 0b0111) == 1: data = data & 0b1000 | 0 + elif (data & 0b0111) == 2: data = data & 0b1000 | 5 + elif (data & 0b0111) == 3: data = data & 0b1000 | 4 + elif (data & 0b0111) == 4: data = data & 0b1000 | 2 + elif (data & 0b0111) == 5: data = data & 0b1000 | 3 + elif _north == 'upper-right': + if (data & 0b0111) == 2: data = data & 0b1000 | 3 + elif (data & 0b0111) == 3: data = data & 0b1000 | 2 + elif (data & 0b0111) == 4: data = data & 0b1000 | 5 + elif (data & 0b0111) == 5: data = data & 0b1000 | 4 + elif _north == 'lower-right': + if (data & 0b0111) == 0: data = data & 0b1000 | 1 + elif (data & 0b0111) == 1: data = data & 0b1000 | 0 + elif (data & 0b0111) == 2: data = data & 0b1000 | 4 + elif (data & 0b0111) == 3: data = data & 0b1000 | 5 + elif (data & 0b0111) == 4: data = data & 0b1000 | 3 + elif (data & 0b0111) == 5: data = data & 0b1000 | 2 + if blockID == 66: # normal minetrack only + #Corners + if _north == 'upper-left': + if data == 6: data = 7 + elif data == 7: data = 8 + elif data == 8: data = 6 + elif data == 9: data = 9 + elif _north == 'upper-right': + if data == 6: data = 8 + elif data == 7: data = 9 + elif data == 8: data = 6 + elif data == 9: data = 7 + elif _north == 'lower-right': + if data == 6: data = 9 + elif data == 7: data = 6 + elif data == 8: data = 8 + elif data == 9: data = 7 + if blockID in (50, 75, 76): # torch, off/on redstone torch + if _north == 'upper-left': + if data == 1: data = 3 + elif data == 2: data = 4 + elif data == 3: data = 2 + elif data == 4: data = 1 + elif _north == 'upper-right': + if data == 1: data = 2 + elif data == 2: data = 1 + elif data == 3: data = 4 + elif data == 4: data = 3 + elif _north == 'lower-right': + if data == 1: data = 4 + elif data == 2: data = 3 + elif data == 3: data = 1 + elif data == 4: data = 2 + if blockID in (53,67): # wooden and cobblestone stairs. + if _north == 'upper-left': + if data == 0: data = 2 + elif data == 1: data = 3 + elif data == 2: data = 1 + elif data == 3: data = 0 + elif _north == 'upper-right': + if data == 0: data = 1 + elif data == 1: data = 0 + elif data == 2: data = 3 + elif data == 3: data = 2 + elif _north == 'lower-right': + if data == 0: data = 3 + elif data == 1: data = 2 + elif data == 2: data = 0 + elif data == 3: data = 1 + if blockID in (61, 62, 23): # furnace and burning furnace + if _north == 'upper-left': + if data == 2: data = 5 + elif data == 3: data = 4 + elif data == 4: data = 2 + elif data == 5: data = 3 + elif _north == 'upper-right': + if data == 2: data = 3 + elif data == 3: data = 2 + elif data == 4: data = 5 + elif data == 5: data = 4 + elif _north == 'lower-right': + if data == 2: data = 4 + elif data == 3: data = 5 + elif data == 4: data = 3 + elif data == 5: data = 2 + if blockID == 63: # signposts + if _north == 'upper-left': + data = (data + 4) % 16 + elif _north == 'upper-right': + data = (data + 8) % 16 + elif _north == 'lower-right': + data = (data + 12) % 16 + if blockID in (64,71): # wooden/iron door + #Masked to not clobber block top/bottom & swung info + if _north == 'upper-left': + if (data & 0b0011) == 0: data = data & 0b1100 | 1 + elif (data & 0b0011) == 1: data = data & 0b1100 | 2 + elif (data & 0b0011) == 2: data = data & 0b1100 | 3 + elif (data & 0b0011) == 3: data = data & 0b1100 | 0 + elif _north == 'upper-right': + if (data & 0b0011) == 0: data = data & 0b1100 | 2 + elif (data & 0b0011) == 1: data = data & 0b1100 | 3 + elif (data & 0b0011) == 2: data = data & 0b1100 | 0 + elif (data & 0b0011) == 3: data = data & 0b1100 | 1 + elif _north == 'lower-right': + if (data & 0b0011) == 0: data = data & 0b1100 | 3 + elif (data & 0b0011) == 1: data = data & 0b1100 | 0 + elif (data & 0b0011) == 2: data = data & 0b1100 | 1 + elif (data & 0b0011) == 3: data = data & 0b1100 | 2 + if blockID == 65: # ladder + if _north == 'upper-left': + if data == 2: data = 5 + elif data == 3: data = 4 + elif data == 4: data = 2 + elif data == 5: data = 3 + elif _north == 'upper-right': + if data == 2: data = 3 + elif data == 3: data = 2 + elif data == 4: data = 5 + elif data == 5: data = 4 + elif _north == 'lower-right': + if data == 2: data = 4 + elif data == 3: data = 5 + elif data == 4: data = 3 + elif data == 5: data = 2 + if blockID == 68: # wall sign + if _north == 'upper-left': + if data == 2: data = 5 + elif data == 3: data = 4 + elif data == 4: data = 2 + elif data == 5: data = 3 + elif _north == 'upper-right': + if data == 2: data = 3 + elif data == 3: data = 2 + elif data == 4: data = 5 + elif data == 5: data = 4 + elif _north == 'lower-right': + if data == 2: data = 4 + elif data == 3: data = 5 + elif data == 4: data = 3 + elif data == 5: data = 2 + if blockID in (86,91): # pumpkins, jack-o-lantern + if _north == 'upper-left': + if data == 0: data = 1 + elif data == 1: data = 2 + elif data == 2: data = 3 + elif data == 3: data = 0 + elif _north == 'upper-right': + if data == 0: data = 2 + elif data == 1: data = 3 + elif data == 2: data = 0 + elif data == 3: data = 1 + elif _north == 'lower-right': + if data == 0: data = 3 + elif data == 1: data = 0 + elif data == 2: data = 1 + elif data == 3: data = 2 + if blockID in (93, 94): # redstone repeaters, ON and OFF + #Masked to not clobber delay info + if _north == 'upper-left': + if (data & 0b0011) == 0: data = data & 0b1100 | 1 + elif (data & 0b0011) == 1: data = data & 0b1100 | 2 + elif (data & 0b0011) == 2: data = data & 0b1100 | 3 + elif (data & 0b0011) == 3: data = data & 0b1100 | 0 + elif _north == 'upper-right': + if (data & 0b0011) == 0: data = data & 0b1100 | 2 + elif (data & 0b0011) == 1: data = data & 0b1100 | 3 + elif (data & 0b0011) == 2: data = data & 0b1100 | 0 + elif (data & 0b0011) == 3: data = data & 0b1100 | 1 + elif _north == 'lower-right': + if (data & 0b0011) == 0: data = data & 0b1100 | 3 + elif (data & 0b0011) == 1: data = data & 0b1100 | 0 + elif (data & 0b0011) == 2: data = data & 0b1100 | 1 + elif (data & 0b0011) == 3: data = data & 0b1100 | 2 + if blockID == 96: # trapdoor + #Masked to not clobber opened/closed info + if _north == 'upper-left': + if (data & 0b0011) == 0: data = data & 0b1100 | 3 + elif (data & 0b0011) == 1: data = data & 0b1100 | 2 + elif (data & 0b0011) == 2: data = data & 0b1100 | 0 + elif (data & 0b0011) == 3: data = data & 0b1100 | 1 + elif _north == 'upper-right': + if (data & 0b0011) == 0: data = data & 0b1100 | 1 + elif (data & 0b0011) == 1: data = data & 0b1100 | 0 + elif (data & 0b0011) == 2: data = data & 0b1100 | 3 + elif (data & 0b0011) == 3: data = data & 0b1100 | 2 + elif _north == 'lower-right': + if (data & 0b0011) == 0: data = data & 0b1100 | 2 + elif (data & 0b0011) == 1: data = data & 0b1100 | 3 + elif (data & 0b0011) == 2: data = data & 0b1100 | 1 + elif (data & 0b0011) == 3: data = data & 0b1100 | 0 + + return data + def tintTexture(im, c): # apparently converting to grayscale drops the alpha channel? i = ImageOps.colorize(ImageOps.grayscale(im), (0,0,0), c) @@ -1722,8 +1956,25 @@ def getBiomeData(worlddir, chunkX, chunkY): ''' global currentBiomeFile, currentBiomeData + biomeX = chunkX // 32 + biomeY = chunkY // 32 + rots = 0 + if _north == 'upper-left': + temp = biomeX + biomeX = biomeY + biomeY = -temp-1 + rots = 3 + elif _north == 'upper-right': + biomeX = -biomeX-1 + biomeY = -biomeY-1 + rots = 2 + elif _north == 'lower-right': + temp = biomeX + biomeX = -biomeY-1 + biomeY = temp + rots = 1 - biomeFile = "b.%d.%d.biome" % (chunkX // 32, chunkY // 32) + biomeFile = "b.%d.%d.biome" % (biomeX, biomeY) if biomeFile == currentBiomeFile: return currentBiomeData @@ -1733,7 +1984,9 @@ def getBiomeData(worlddir, chunkX, chunkY): # make sure the file size is correct if not len(rawdata) == 512 * 512 * 2: raise Exception("Biome file %s is not valid." % (biomeFile,)) - data = numpy.frombuffer(rawdata, dtype=numpy.dtype(">u2")) + data = numpy.reshape(numpy.rot90(numpy.reshape( + numpy.frombuffer(rawdata, dtype=numpy.dtype(">u2")), + (512,512)),rots), -1) except IOError: data = None pass # no biome data @@ -1822,7 +2075,10 @@ biome_tall_fern_texture = None biome_leaf_texture = None specialblockmap = None -def generate(path=None,texture_size=24,bgc = (26,26,26,0)): +def generate(path=None,texture_size=24,bgc = (26,26,26,0),north_direction='lower-left'): + global _north + _north = north_direction + global _find_file_local_path global bgcolor bgcolor = bgc global _find_file_local_path, texture_dimensions diff --git a/overviewer_core/world.py b/overviewer_core/world.py index ad98328..59b77c5 100644 --- a/overviewer_core/world.py +++ b/overviewer_core/world.py @@ -69,10 +69,11 @@ class World(object): mincol = maxcol = minrow = maxrow = 0 - def __init__(self, worlddir, outputdir, useBiomeData=False,regionlist=None): + def __init__(self, worlddir, outputdir, useBiomeData=False, regionlist=None, north_direction="lower-left"): self.worlddir = worlddir self.outputdir = outputdir self.useBiomeData = useBiomeData + self.north_direction = north_direction #find region files, or load the region list #this also caches all the region file header info @@ -169,6 +170,18 @@ class World(object): data = nbt.read_all() level = data[1]['Level'] chunk_data = level + chunk_data['Blocks'] = numpy.array(numpy.rot90(numpy.frombuffer( + level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)), + self._get_north_rotations())) + chunk_data['Data'] = numpy.array(numpy.rot90(numpy.frombuffer( + level['Data'], dtype=numpy.uint8).reshape((16,16,64)), + self._get_north_rotations())) + chunk_data['SkyLight'] = numpy.array(numpy.rot90(numpy.frombuffer( + level['SkyLight'], dtype=numpy.uint8).reshape((16,16,64)), + self._get_north_rotations())) + chunk_data['BlockLight'] = numpy.array(numpy.rot90(numpy.frombuffer( + level['BlockLight'], dtype=numpy.uint8).reshape((16,16,64)), + self._get_north_rotations())) #chunk_data = {} #chunk_data['skylight'] = chunk.get_skylight_array(level) #chunk_data['blocklight'] = chunk.get_blocklight_array(level) @@ -185,7 +198,7 @@ class World(object): if self.regions.get(filename) is not None: self.regions[filename][0].closefile() chunkcache = {} - mcr = nbt.MCRFileReader(filename) + mcr = nbt.MCRFileReader(filename, self.north_direction) self.regions[filename] = (mcr,os.path.getmtime(filename),chunkcache) return mcr @@ -201,7 +214,7 @@ class World(object): in the image each one should be. Returns (col, row).""" # columns are determined by the sum of the chunk coords, rows are the - # difference (TODO: be able to change direction of north) + # difference # change this function, and you MUST change unconvert_coords return (chunkx + chunky, chunky - chunkx) @@ -219,9 +232,20 @@ class World(object): ## read spawn info from level.dat data = nbt.load(os.path.join(self.worlddir, "level.dat"))[1] - spawnX = data['Data']['SpawnX'] + disp_spawnX = spawnX = data['Data']['SpawnX'] spawnY = data['Data']['SpawnY'] - spawnZ = data['Data']['SpawnZ'] + disp_spawnZ = spawnZ = data['Data']['SpawnZ'] + if self.north_direction == 'upper-left': + temp = spawnX + spawnX = -spawnZ + spawnZ = temp + elif self.north_direction == 'upper-right': + spawnX = -spawnX + spawnZ = -spawnZ + elif self.north_direction == 'lower-right': + temp = spawnX + spawnX = spawnZ + spawnZ = -temp ## The chunk that holds the spawn location chunkX = spawnX/16 @@ -231,7 +255,7 @@ class World(object): ## The filename of this chunk chunkFile = self.get_region_path(chunkX, chunkY) if chunkFile is not None: - data = nbt.load_from_region(chunkFile, chunkX, chunkY)[1] + data = nbt.load_from_region(chunkFile, chunkX, chunkY, self.north_direction)[1] if data is not None: level = data['Level'] blockArray = numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)) @@ -248,9 +272,9 @@ class World(object): except ChunkCorrupt: #ignore corrupt spawn, and continue pass - self.POI.append( dict(x=spawnX, y=spawnY, z=spawnZ, + self.POI.append( dict(x=disp_spawnX, y=spawnY, z=disp_spawnZ, msg="Spawn", type="spawn", chunk=(chunkX, chunkY))) - self.spawn = (spawnX, spawnY, spawnZ) + self.spawn = (disp_spawnX, spawnY, disp_spawnZ) def go(self, procs): """Scan the world directory, to fill in @@ -296,6 +320,16 @@ class World(object): self.findTrueSpawn() + def _get_north_rotations(self): + if self.north_direction == 'upper-left': + return 1 + elif self.north_direction == 'upper-right': + return 2 + elif self.north_direction == 'lower-right': + return 3 + elif self.north_direction == 'lower-left': + return 0 + def _iterate_regionfiles(self,regionlist=None): """Returns an iterator of all of the region files, along with their coordinates @@ -312,7 +346,20 @@ class World(object): if f.startswith("r.") and f.endswith(".mcr"): p = f.split(".") logging.debug("Using path %s from regionlist", f) - yield (int(p[1]), int(p[2]), join(self.worlddir, 'region', f)) + x = int(p[1]) + y = int(p[2]) + if self.north_direction == 'upper-left': + temp = x + x = -y-1 + y = temp + elif self.north_direction == 'upper-right': + x = -x-1 + y = -y-1 + elif self.north_direction == 'lower-right': + temp = x + x = y + y = -temp-1 + yield (x, y, join(self.worlddir, 'region', f)) else: logging.warning("Ignore path '%s' in regionlist", f) @@ -320,7 +367,20 @@ class World(object): 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)) + x = int(p[1]) + y = int(p[2]) + if self.north_direction == 'upper-left': + temp = x + x = -y-1 + y = temp + elif self.north_direction == 'upper-right': + x = -x-1 + y = -y-1 + elif self.north_direction == 'lower-right': + temp = x + x = y + y = -temp-1 + yield (x, y, join(dirpath, f)) def get_save_dir(): """Returns the path to the local saves directory diff --git a/sample.settings.py b/sample.settings.py index 3b6aed2..cccdac8 100644 --- a/sample.settings.py +++ b/sample.settings.py @@ -152,6 +152,14 @@ verbose = 1 if "web_assets_hook" in locals(): skipjs = True +################################################################################ +### north_direction +## Make north point somewhere else! +## Valid options are 'lower-left', 'upper-left', 'upper-right', 'upper-left' +## default: lower-left +## Type: string +## Example: +north_direction = "upper-right"