diff --git a/quadtree.py b/quadtree.py index 8aaf8b4..0e02221 100644 --- a/quadtree.py +++ b/quadtree.py @@ -22,6 +22,7 @@ import functools import re import shutil import collections +import json from PIL import Image @@ -119,6 +120,11 @@ class QuadtreeGen(object): with open(os.path.join(self.destdir, "index.html"), 'w') as output: output.write(html) + + + with open(os.path.join(self.destdir, "markers.js"), 'w') as output: + output.write("var markerData=%s" % json.dumps(self.world.POI)) + def _get_cur_depth(self): """How deep is the quadtree currently in the destdir? This glances in index.html to see what maxZoom is set to. diff --git a/template.html b/template.html index c0e5bc9..ef2868f 100644 --- a/template.html +++ b/template.html @@ -7,6 +7,7 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } #mcmap { height: 100% } + @@ -74,7 +75,57 @@ }; var map; - + var prot; + + var markersInit = false; + + function convertCoords (x,y,z) { + + var imgx = 0; + var imgy = 0; + + imgx = imgx + (12*x); + imgy = imgy - (6*x); + + imgx = imgx + (12 * y); + imgy = imgy + (6* y); + + imgy = imgy - (12*z); + + // this math is mysterious. i don't fully understand it + // but the idea is to assume that block 0,0,0 in chunk 0,0 + // is drawn in the very middle of the gmap at (192,192) + return [192*Math.pow(2,config.maxZoom)+imgx, 192*Math.pow(2,config.maxZoom)+imgy+768+768]; + } + + function initMarkers() { + if (markersInit) { return; } + + markersInit = true; + + prot = map.getProjection(); + + for (i in markerData) { + var item = markerData[i]; + + var converted = convertCoords(item.x-16, item.z, item.y); + + + var x = converted[0] / Math.pow(2, config.maxZoom); + var y = converted[1] / Math.pow(2, config.maxZoom); + var p = new google.maps.Point(x,y); + var marker = new google.maps.Marker({ +position: prot.fromPointToLatLng(p), +map: map, +title:item.msg +}); + +} + + + +} + function initialize() { var mapOptions = { zoom: config.defaultZoom, @@ -89,12 +140,33 @@ if(config.debug) { map.overlayMapTypes.insertAt(0, new CoordMapType(new google.maps.Size(config.tileSize, config.tileSize))); } + + // Now attach the coordinate map type to the map's registry map.mapTypes.set('mcmap', MCMapType); // We can now set the map to use the 'coordinate' map type map.setMapTypeId('mcmap'); + + prot = map.getProjection(); + + if (config.debug) + google.maps.event.addListener(map, 'click', function(event) { + console.log("latLng: " + event.latLng.lat() + ", " + event.latLng.lng()); + var pnt = prot.fromLatLngToPoint(event.latLng); + + console.log("point: " + pnt);// + var pxx = pnt.x * Math.pow(2,config.maxZoom); + var pxy = pnt.y * Math.pow(2,config.maxZoom); + console.log("pixel: " + pxx + ", " + pxy); + }); + + google.maps.event.addListener(map, 'projection_changed', function(event) { + initMarkers(); + }); + + } diff --git a/textures.py b/textures.py index ef75a0c..aa5c31a 100644 --- a/textures.py +++ b/textures.py @@ -151,7 +151,7 @@ def _transform_image_side(img): return newimg -def _build_block(top, side): +def _build_block(top, side, texID=None): """From a top texture and a side texture, build a block image. top and side should be 16x16 image objects. Returns a 24x24 image @@ -178,6 +178,14 @@ def _build_block(top, side): otherside = ImageEnhance.Brightness(otherside).enhance(0.8) otherside.putalpha(othersidealpha) + ## special case for non-block things + if texID in (12,13,15,28,29,80,73): ## flowers, sapling, mushrooms, regular torch, reeds + # instead of pasting these blocks at the cube edges, place them in the middle: + # and omit the top + img.paste(side, (6,3), side) + img.paste(otherside, (6,3), otherside) + return img + img.paste(side, (0,6), side) img.paste(otherside, (12,6), otherside) img.paste(top, (0,0), top) @@ -203,21 +211,35 @@ def _build_blockimages(): # Top textures of all block types. The number here is the index in the # texture array (terrain_images), which comes from terrain.png's cells, left to right top to # bottom. - topids = [-1,1,0,2,16,4,15,17,205,205,237,237,18,19,32,33, - 34,21,52,48,49,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, # Cloths are left out - -1,-1,-1,64,64,13,12,29,28,23,22,6,6,7,8,35, # Gold/iron blocks? Doublestep? TNT from above? - 36,37,-1,-1,65,4,25,101,98,24,43,-1,86,1,1,-1, # Torch from above? leaving out fire. Redstone wire? Crops left out. sign post - -1,-1,-1,16,-1,-1,-1,-1,-1,51,51,-1,-1,1,66,67, # door,ladder left out. Minecart rail orientation - 66,69,72,-1,74 # clay? + # 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, + # 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + 34, 21, 52, 48, 49, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, # Cloths are left out + # 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 + -1, -1, -1, 64, 64, 13, 12, 29, 28, 23, 22, 6, 6, 7, 8, 35, # Gold/iron blocks? Doublestep? TNT from above? + # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 + 36, 37, 80, -1, 65, 4, 25,101, 98, 24, 43, -1, 86, 1, 1, -1, # Torch from above? leaving out fire. Redstone wire? Crops left out. sign post + # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 + -1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, 1, 66, 67, # door,ladder left out. Minecart rail orientation + # 80 81 82 83 84 + 66, 69, 72, 73, 74 # clay? ] + # NOTE: For non-block textures, the sideid is ignored, but can't be -1 + # And side textures of all block types - sideids = [-1,1,3,2,16,4,15,17,205,205,237,237,18,19,32,33, - 34,20,52,48,49,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, - -1,-1,-1,64,64,13,12,29,28,23,22,6,6,7,8,35, - 36,37,-1,-1,65,4,25,101,98,24,43,-1,86,1,1,-1, - -1,-1,-1,16,-1,-1,-1,-1,-1,51,51,-1,-1,1,66,67, - 66,69,72,-1,74 + # 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, + # 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 + 34, 20, 52, 48, 49, -1, -1, -1, -1, -1, -1, -1,- 1, -1, -1, -1, + # 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 + -1, -1, -1, 64, 64, 13, 12, 29, 28, 23, 22, 6, 6, 7, 8, 35, + # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 + 36, 37, 80, -1, 65, 4, 25,101, 98, 24, 43, -1, 86, 1, 1, -1, + # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 + -1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, 1, 66, 67, + # 80 81 82 83 84 + 66, 69, 72, 73, 74 ] # This maps block id to the texture that goes on the side of the block @@ -230,7 +252,7 @@ def _build_blockimages(): toptexture = terrain_images[toptextureid] sidetexture = terrain_images[sidetextureid] - img = _build_block(toptexture, sidetexture) + img = _build_block(toptexture, sidetexture, toptextureid) allimages.append((img.convert("RGB"), img.split()[3])) diff --git a/world.py b/world.py index 7df3494..8e44635 100644 --- a/world.py +++ b/world.py @@ -17,6 +17,7 @@ import functools import os import os.path import multiprocessing +import numpy from PIL import Image @@ -57,6 +58,29 @@ def _convert_coords(chunks): return mincol, maxcol, minrow, maxrow, chunks_translated + +def base36encode(number, alphabet='0123456789abcdefghijklmnopqrstuvwxyz'): + ''' + Convert an integer to a base36 string. + ''' + if not isinstance(number, (int, long)): + raise TypeError('number must be an integer') + + newn = abs(number) + + # Special case for zero + if number == 0: + return '0' + + base36 = '' + while newn != 0: + newn, i = divmod(newn, len(alphabet)) + base36 = alphabet[i] + base36 + + if number < 0: + return "-" + base36 + return base36 + class WorldRenderer(object): """Renders a world's worth of chunks. worlddir is the path to the minecraft world @@ -67,6 +91,47 @@ class WorldRenderer(object): self.caves = False self.cachedir = cachedir + # stores Points Of Interest to be mapped with markers + # a list of dictionaries, see below for an example + self.POI = [] + + def findTrueSpawn(self): + """Adds the true spawn location to self.POI. The spawn Y coordinate + is almost always the default of 64. Find the first air block above + that point for the true spawn location""" + + ## read spawn info from level.dat + data = nbt.load(os.path.join(self.worlddir, "level.dat"))[1] + spawnX = data['Data']['SpawnX'] + spawnY = data['Data']['SpawnY'] + spawnZ = data['Data']['SpawnZ'] + + ## The chunk that holds the spawn location + chunkX = spawnX/16 + chunkY = spawnZ/16 + + ## The filename of this chunk + chunkFile = "%s/%s/c.%s.%s.dat" % (base36encode(chunkX % 64), + base36encode(chunkY % 64), + base36encode(chunkX), + base36encode(chunkY)) + + + data=nbt.load(os.path.join(self.worlddir, chunkFile))[1] + level = data['Level'] + blockArray = numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)) + + ## The block for spawn *within* the chunk + inChunkX = spawnX - (chunkX*16) + inChunkZ = spawnZ - (chunkY*16) + + ## find the first air block + while (blockArray[inChunkX, inChunkZ, spawnY] != 0): + spawnY += 1 + + + self.POI.append( dict(x=spawnX, y=spawnY, z=spawnZ, msg="Spawn")) + def go(self, procs): """Starts the render. This returns when it is finished""" @@ -84,6 +149,8 @@ class WorldRenderer(object): self.minrow = minrow self.maxrow = maxrow + self.findTrueSpawn() + def _find_chunkfiles(self): """Returns a list of all the chunk file locations, and the file they correspond to.