diff --git a/contrib/testRender.py b/contrib/testRender.py index 7646dad..2ea0e87 100644 --- a/contrib/testRender.py +++ b/contrib/testRender.py @@ -131,7 +131,8 @@ try: print "... done" stats = get_stats(timelist) print stats - log.write("%s %s\n" % (commit, repr(stats))) + if log: + log.write("%s %s\n" % (commit, repr(stats))) except CalledProcessError, e: if options.fatal_errors: print diff --git a/overviewer.py b/overviewer.py index e30b85c..a80ef27 100755 --- a/overviewer.py +++ b/overviewer.py @@ -27,22 +27,51 @@ import subprocess import multiprocessing import time import logging +import platform +from overviewer_core import util logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s") +this_dir = util.get_program_path() + # make sure the c_overviewer extension is available try: from overviewer_core import c_overviewer except ImportError: + ## try to find the build extension + ext = os.path.join(this_dir, "c_overviewer.%s" % ("pyd" if platform.system() == "Windows" else "so")) + if os.path.exists(ext): + print "Something has gone wrong importing the c_overviewer extension. Please" + print "make sure it is up-to-date (clean and rebuild)" + sys.exit(1) + print "You need to compile the c_overviewer module to run Minecraft Overviewer." print "Run `python setup.py build`, or see the README for details." import traceback traceback.print_exc() sys.exit(1) + +if hasattr(sys, "frozen"): + pass # we don't bother with a compat test since it should always be in sync +elif "extension_version" in dir(c_overviewer): + # check to make sure the binary matches the headers + if os.path.exists(os.path.join(this_dir, "src", "overviewer.h")): + with open(os.path.join(this_dir, "src", "overviewer.h")) as f: + lines = f.readlines() + lines = filter(lambda x: x.startswith("#define OVERVIEWER_EXTENSION_VERSION"), lines) + if lines: + l = lines[0] + if int(l.split()[2].strip()) != c_overviewer.extension_version(): + print "Please rebuild your c_overviewer module. It is out of date!" + sys.exit(1) +else: + print "Please rebuild your c_overviewer module. It is out of date!" + sys.exit(1) + from overviewer_core.configParser import ConfigOptionParser from overviewer_core import optimizeimages, world, quadtree -from overviewer_core import googlemap, rendernode, util +from overviewer_core import googlemap, rendernode helptext = """ @@ -99,7 +128,7 @@ def main(): sys.exit(0) if len(args) < 1: - print "You need to give me your world number or directory" + logging.error("You need to give me your world number or directory") parser.print_help() list_worlds() sys.exit(1) @@ -112,7 +141,7 @@ def main(): # if there are no worlds found at all, exit now if not worlds: parser.print_help() - print "\nInvalid world path" + logging.error("Invalid world path") sys.exit(1) try: @@ -125,12 +154,12 @@ def main(): except KeyError: # it's not a number, name, or path parser.print_help() - print "Invalid world name or path" + logging.error("Invalid world name or path") sys.exit(1) except KeyError: # it was an invalid number parser.print_help() - print "Invalid world number" + logging.error("Invalid world number") sys.exit(1) if len(args) != 2: @@ -183,6 +212,8 @@ def main(): w = world.World(worlddir, useBiomeData=useBiomeData) w.go(options.procs) + logging.info("Rending the following tilesets: %s", ",".join(options.rendermode)) + # create the quadtrees # TODO chunklist q = [] diff --git a/overviewer_core/configParser.py b/overviewer_core/configParser.py index cbdbc4c..88925d7 100644 --- a/overviewer_core/configParser.py +++ b/overviewer_core/configParser.py @@ -1,6 +1,7 @@ from optparse import OptionParser import sys import os.path +import logging class OptionsResults(object): pass @@ -22,10 +23,12 @@ class ConfigOptionParser(object): self.requiredArgs = [] def display_config(self): + logging.info("Using the following settings:") for x in self.configVars: n = x['dest'] print "%s: %r" % (n, self.configResults.__dict__[n]) + def add_option(self, *args, **kwargs): if kwargs.get("configFileOnly", False) and kwargs.get("commandLineOnly", False): @@ -71,14 +74,14 @@ class ConfigOptionParser(object): except NameError, ex: import traceback traceback.print_exc() - print "\nError parsing %s. Please check the trackback above" % self.configFile + logging.error("Error parsing %s. Please check the trackback above" % self.configFile) sys.exit(1) except SyntaxError, ex: import traceback traceback.print_exc() tb = sys.exc_info()[2] #print tb.tb_frame.f_code.co_filename - print "\nError parsing %s. Please check the trackback above" % self.configFile + logging.error("Error parsing %s. Please check the trackback above" % self.configFile) sys.exit(1) #print l.keys() @@ -89,7 +92,7 @@ class ConfigOptionParser(object): n = a['dest'] if a.get('commandLineOnly', False): if n in l.keys(): - print "Error: %s can only be specified on the command line. It is not valid in the config file" % n + logging.error("Error: %s can only be specified on the command line. It is not valid in the config file" % n) sys.exit(1) configResults.__dict__[n] = l.get(n) @@ -113,7 +116,8 @@ class ConfigOptionParser(object): for a in self.configVars: n = a['dest'] if configResults.__dict__[n] == None and a.get('required',False): - raise Exception("%s is required" % n) + logging.error("%s is required" % n) + sys.exit(1) # sixth, check types for a in self.configVars: @@ -128,7 +132,7 @@ class ConfigOptionParser(object): try: configResults.__dict__[n] = self.checkType(configResults.__dict__[n], a) except ValueError, ex: - print "There was a problem converting the value '%s' to type %s for config parameter '%s'" % (configResults.__dict__[n], a['type'], n) + logging.error("There was a problem converting the value '%s' to type %s for config parameter '%s'" % (configResults.__dict__[n], a['type'], n)) import traceback #traceback.print_exc() sys.exit(1) @@ -153,7 +157,7 @@ class ConfigOptionParser(object): return long(value) elif a['type'] == "choice": if value not in a['choices']: - print "The value '%s' is not valid for config parameter '%s'" % (value, a['dest']) + logging.error("The value '%s' is not valid for config parameter '%s'" % (value, a['dest'])) sys.exit(1) return value elif a['type'] == "float": @@ -164,5 +168,5 @@ class ConfigOptionParser(object): if not callable(value): raise ValueError("Not callable") else: - print "Unknown type!" + logging.error("Unknown type!") sys.exit(1) diff --git a/overviewer_core/quadtree.py b/overviewer_core/quadtree.py index 2437b33..ac1c26c 100644 --- a/overviewer_core/quadtree.py +++ b/overviewer_core/quadtree.py @@ -222,6 +222,10 @@ class QuadtreeGen(object): unconvert_coords = self.world.unconvert_coords #get_region_path = self.world.get_region_path get_region = self.world.regionfiles.get + regionx = None + regiony = None + c = None + mcr = None for row in xrange(rowstart-16, rowend+1): for col in xrange(colstart, colend+1): # due to how chunks are arranged, we can only allow @@ -230,12 +234,18 @@ class QuadtreeGen(object): if row % 2 != col % 2: continue - # return (col, row, chunkx, chunky, regionpath) chunkx, chunky = unconvert_coords(col, row) - #c = get_region_path(chunkx, chunky) - _, _, c, mcr = get_region((chunkx//32, chunky//32),(None,None,None,None)); + + regionx_ = chunkx//32 + regiony_ = chunky//32 + if regionx_ != regionx or regiony_ != regiony: + regionx = regionx_ + regiony = regiony_ + _, _, c, mcr = get_region((regionx, regiony),(None,None,None,None)) + if c is not None and mcr.chunkExists(chunkx,chunky): chunklist.append((col, row, chunkx, chunky, c)) + return chunklist def get_worldtiles(self): diff --git a/overviewer_core/src/main.c b/overviewer_core/src/main.c index e2be7a3..ddbe0af 100644 --- a/overviewer_core/src/main.c +++ b/overviewer_core/src/main.c @@ -17,6 +17,11 @@ #include "overviewer.h" +PyObject *get_extension_version(PyObject *self, PyObject *args) { + + return Py_BuildValue("i", OVERVIEWER_EXTENSION_VERSION); +} + static PyMethodDef COverviewerMethods[] = { {"alpha_over", alpha_over_wrap, METH_VARARGS, "alpha over composite function"}, @@ -26,9 +31,12 @@ static PyMethodDef COverviewerMethods[] = { "returns available render modes"}, {"get_render_mode_info", get_render_mode_info, METH_VARARGS, "returns info for a particular render mode"}, + {"extension_version", get_extension_version, METH_VARARGS, + "Returns the extension version"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; + PyMODINIT_FUNC initc_overviewer(void) { diff --git a/overviewer_core/src/overviewer.h b/overviewer_core/src/overviewer.h index dc1c31e..c978773 100644 --- a/overviewer_core/src/overviewer.h +++ b/overviewer_core/src/overviewer.h @@ -24,6 +24,10 @@ #ifndef __OVERVIEWER_H_INCLUDED__ #define __OVERVIEWER_H_INCLUDED__ +// increment this value if you've made a change to the c extesion +// and want to force users to rebuild +#define OVERVIEWER_EXTENSION_VERSION 2 + /* Python PIL, and numpy headers */ #include #include diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index fa1ac68..a94030c 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -285,7 +285,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None # first back sides if side1 != None : side1 = transform_image_side(side1, blockID) - side1 = side1.transpose(Image.FLIP_TOP_BOTTOM) + side1 = side1.transpose(Image.FLIP_LEFT_RIGHT) # Darken this side. sidealpha = side1.split()[3] @@ -297,8 +297,6 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None if side2 != None : side2 = transform_image_side(side2, blockID) - side2 = side2.transpose(Image.FLIP_LEFT_RIGHT) - side2 = side2.transpose(Image.FLIP_TOP_BOTTOM) # Darken this side. sidealpha2 = side2.split()[3] @@ -356,9 +354,9 @@ def _build_blockimages(): # 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? # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 - 36, 37, 80, -1, 65, 4, 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, 4, 25, -1, 98, 24, -1, -1, 86, -1, -1, -1, # Torch from above? leaving out fire. Redstone wire? Crops/furnaces handled elsewhere. 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,115, 99, -1, 66, 67, # door,ladder left out. Minecart rail orientation, redstone torches + -1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67, # door,ladder left out. Minecart rail orientation, redstone torches # 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? ] @@ -373,9 +371,9 @@ def _build_blockimages(): # 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, 80, -1, 65, 4, 25,101, 98, 24, -1, -1, 86, -1, -1, -1, + 36, 37, -1, -1, 65, 4, 25,101, 98, 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, 16, -1, -1, -1, -1, -1, 51, 51,115, 99, -1, 66, 67, + -1, -1, -1, 16, -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 ] @@ -431,242 +429,45 @@ load_water() def generate_special_texture(blockID, data): """Generates a special texture, such as a correctly facing minecraft track""" #print "%s has ancillary data: %X" %(blockID, data) - # TODO torches, redstone torches, crops, ladders, stairs, - # levers, doors, buttons, and signs all need to be handled here (and in chunkpy) - if blockID == 66: # minetrack: - raw_straight = terrain_images[128] - raw_corner = terrain_images[112] - - ## use transform_image to scale and shear - if data == 0: - track = transform_image(raw_straight, blockID) - elif data == 6: - track = transform_image(raw_corner, blockID) - elif data == 7: - track = transform_image(raw_corner.rotate(270), blockID) - elif data == 8: - # flip - track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90), - blockID) - elif data == 9: - track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM), - blockID) - elif data == 1: - track = transform_image(raw_straight.rotate(90), blockID) - - #slopes - elif data == 2: # slope going up in +x direction - track = transform_image_slope(raw_straight,blockID) - track = track.transpose(Image.FLIP_LEFT_RIGHT) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, track, (2,0), track) - # the 2 pixels move is needed to fit with the adjacent tracks - return (img.convert("RGB"), img.split()[3]) - - elif data == 3: # slope going up in -x direction - # tracks are sprites, in this case we are seeing the "side" of - # the sprite, so draw a line to make it beautiful. - img = Image.new("RGBA", (24,24), (38,92,255,0)) - ImageDraw.Draw(img).line([(11,11),(23,17)],fill=(164,164,164)) - # grey from track texture (exterior grey). - # the track doesn't start from image corners, be carefull drawing the line! - return (img.convert("RGB"), img.split()[3]) - - elif data == 4: # slope going up in -y direction - track = transform_image_slope(raw_straight,blockID) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, track, (0,0), track) - return (img.convert("RGB"), img.split()[3]) - - elif data == 5: # slope going up in +y direction - # same as "data == 3" - img = Image.new("RGBA", (24,24), (38,92,255,0)) - ImageDraw.Draw(img).line([(1,17),(12,11)],fill=(164,164,164)) - return (img.convert("RGB"), img.split()[3]) - - - else: # just in case - track = transform_image(raw_straight, blockID) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, track, (0,12), track) - - return (img.convert("RGB"), img.split()[3]) - - if blockID == 58: # crafting table - top = terrain_images[43] - side3 = terrain_images[43+16] - side4 = terrain_images[43+16+1] - - img = _build_full_block(top, None, None, side3, side4, None, 58) - return (img.convert("RGB"), img.split()[3]) - - if blockID == 59: # crops - raw_crop = terrain_images[88+data] - crop1 = transform_image(raw_crop, blockID) - crop2 = transform_image_side(raw_crop, blockID) - crop3 = crop2.transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, crop1, (0,12), crop1) - composite.alpha_over(img, crop2, (6,3), crop2) - composite.alpha_over(img, crop3, (6,3), crop3) - 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) - - 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) - return (img.convert("RGB"), img.split()[3]) - - if blockID in (86,91): # jack-o-lantern - top = transform_image(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) - - 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) - return (img.convert("RGB"), img.split()[3]) - - 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) - - 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) - 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)) - - 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]) - - - - if blockID == 65: # ladder - raw_texture = terrain_images[83] - #print "ladder is facing: %d" % data - if data == 5: - # normally this ladder would be obsured by the block it's attached to - # but since ladders can apparently be placed on transparent blocks, we - # have to render this thing anyway. same for data == 2 - tex = transform_image_side(raw_texture) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, tex, (0,6), tex) - return (img.convert("RGB"), img.split()[3]) - if data == 2: - tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, tex, (12,6), tex) - return (img.convert("RGB"), img.split()[3]) - if data == 3: - tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, tex, (0,0), tex) - return (img.convert("RGB"), img.split()[3]) - if data == 4: - tex = transform_image_side(raw_texture) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, tex, (12,0), tex) - return (img.convert("RGB"), img.split()[3]) - - if blockID in (64,71): #wooden door, or iron door - if data & 0x8 == 0x8: # top of the door - raw_door = terrain_images[81 if blockID == 64 else 82] - else: # bottom of the door - raw_door = terrain_images[97 if blockID == 64 else 98] - - # if you want to render all doors as closed, then force - # force swung to be False - if data & 0x4 == 0x4: - swung=True - else: - swung=False - - # mask out the high bits to figure out the orientation - img = Image.new("RGBA", (24,24), (38,92,255,0)) - if (data & 0x03) == 0: - if not swung: - tex = transform_image_side(raw_door) - composite.alpha_over(img, tex, (0,6), tex) - else: - # flip first to set the doornob on the correct side - tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) - tex = tex.transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (0,0), tex) - - if (data & 0x03) == 1: - if not swung: - tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (0,0), tex) - else: - tex = transform_image_side(raw_door) - composite.alpha_over(img, tex, (12,0), tex) - - if (data & 0x03) == 2: - if not swung: - tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) - composite.alpha_over(img, tex, (12,0), tex) - else: - tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (12,6), tex) - - if (data & 0x03) == 3: - if not swung: - tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (12,6), tex) - else: - tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) - composite.alpha_over(img, tex, (0,6), tex) - - return (img.convert("RGB"), img.split()[3]) + # TODO ladders, stairs, levers, buttons, and signs + # 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) return (img.convert("RGB"), img.split()[3]) - - if blockID == 51: # fire - firetexture = _load_image("fire.png") - side1 = transform_image_side(firetexture) - side2 = transform_image_side(firetexture).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, side1, (12,0), side1) - composite.alpha_over(img, side2, (0,0), side2) - composite.alpha_over(img, side1, (0,6), side1) - composite.alpha_over(img, side2, (12,6), side2) + if blockID == 9: # spring water, flowing water and waterfall water + + watertexture = _load_image("water.png") - return (img.convert("RGB"), img.split()[3]) - - if blockID == 18: # leaves - t = tintTexture(terrain_images[52], (37, 118, 25)) - img = _build_block(t, t, 18) - return (img.convert("RGB"), img.split()[3]) + if (data & 0b10000) == 16: + top = watertexture + + else: top = None + + if (data & 0b0001) == 1: + side1 = watertexture # top left + else: side1 = None + if (data & 0b1000) == 8: + side2 = watertexture # top right + else: side2 = None + + if (data & 0b0010) == 2: + side3 = watertexture # bottom left + else: side3 = None + + if (data & 0b0100) == 4: + side4 = watertexture # bottom right + else: side4 = None + + img = _build_full_block(top,side1,side2,side3,side4) + + return (img.convert("RGB"),img.split()[3]) + + if blockID == 17: # wood: normal, birch and pines top = terrain_images[21] if data == 0: @@ -681,7 +482,26 @@ def generate_special_texture(blockID, data): side = terrain_images[117] img = _build_block(top, side, 17) return (img.convert("RGB"), img.split()[3]) + + + if blockID == 18: # leaves + t = tintTexture(terrain_images[52], (37, 118, 25)) + 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)) + + 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]) + + if blockID == 35: # wool if data == 0: # white top = side = terrain_images[64] @@ -748,7 +568,370 @@ def generate_special_texture(blockID, data): img = _build_block(top, side, 35) return (img.convert("RGB"), img.split()[3]) + + if blockID in (43,44): # slab and double-slab + if data == 0: # stone slab + top = terrain_images[6] + side = terrain_images[5] + img = _build_block(top, side, blockID) + return (img.convert("RGB"), img.split()[3]) + + if data == 1: # stone slab + top = terrain_images[176] + side = terrain_images[192] + img = _build_block(top, side, blockID) + return (img.convert("RGB"), img.split()[3]) + + if data == 2: # wooden slab + top = side = terrain_images[4] + img = _build_block(top, side, blockID) + return (img.convert("RGB"), img.split()[3]) + + if data == 3: # cobblestone slab + top = side = terrain_images[16] + img = _build_block(top, side, blockID) + return (img.convert("RGB"), img.split()[3]) + + + if blockID in (50,75,76): # torch, off redstone torch, on redstone torch + + # choose the proper texture + if blockID == 50: # torch + small = terrain_images[80] + elif blockID == 75: # off redstone torch + small = terrain_images[115] + else: # on redstone torch + small = terrain_images[99] + + # compose a torch bigger than the normal + # (better for doing transformations) + torch = Image.new("RGBA", (16,16), (38,92,255,0)) + composite.alpha_over(torch,small,(-4,-3)) + composite.alpha_over(torch,small,(-5,-2)) + composite.alpha_over(torch,small,(-3,-2)) + + # angle of inclination of the texture + rotation = 15 + + if data == 1: # pointing south + torch = torch.rotate(-rotation, Image.NEAREST) # nearest filter is more nitid. + img = _build_full_block(None, None, None, torch, None, None, blockID) + + elif data == 2: # pointing north + torch = torch.rotate(rotation, Image.NEAREST) + img = _build_full_block(None, None, torch, None, None, None, blockID) + + elif data == 3: # pointing west + torch = torch.rotate(rotation, Image.NEAREST) + img = _build_full_block(None, torch, None, None, None, None, blockID) + + elif data == 4: # pointing east + torch = torch.rotate(-rotation, Image.NEAREST) + img = _build_full_block(None, None, None, None, torch, None, blockID) + + elif data == 5: # standing on the floor + # compose a "3d torch". + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + small_crop = small.crop((2,2,14,14)) + slice = small_crop.copy() + ImageDraw.Draw(slice).rectangle((6,0,12,12),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(slice).rectangle((0,0,4,12),outline=(0,0,0,0),fill=(0,0,0,0)) + + composite.alpha_over(img, slice, (6,4)) + composite.alpha_over(img, small_crop, (5,5)) + composite.alpha_over(img, small_crop, (6,5)) + composite.alpha_over(img, slice, (6,6)) + + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 51: # fire + firetexture = _load_image("fire.png") + side1 = transform_image_side(firetexture) + side2 = transform_image_side(firetexture).transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + composite.alpha_over(img, side1, (12,0), side1) + composite.alpha_over(img, side2, (0,0), side2) + + composite.alpha_over(img, side1, (0,6), side1) + composite.alpha_over(img, side2, (12,6), side2) + + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 55: # redstone wire + + if data & 0b1000000 == 64: # powered redstone wire + redstone_wire_t = terrain_images[165] + redstone_wire_t = tintTexture(redstone_wire_t,(255,0,0)) + + redstone_cross_t = terrain_images[164] + redstone_cross_t = tintTexture(redstone_cross_t,(255,0,0)) + + + else: # unpowered redstone wire + redstone_wire_t = terrain_images[165] + redstone_wire_t = tintTexture(redstone_wire_t,(48,0,0)) + + redstone_cross_t = terrain_images[164] + redstone_cross_t = tintTexture(redstone_cross_t,(48,0,0)) + + # generate an image per redstone direction + branch_top_left = redstone_cross_t.copy() + ImageDraw.Draw(branch_top_left).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_left).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_left).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + branch_top_right = redstone_cross_t.copy() + ImageDraw.Draw(branch_top_right).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_right).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_right).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + branch_bottom_right = redstone_cross_t.copy() + ImageDraw.Draw(branch_bottom_right).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_right).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_right).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + branch_bottom_left = redstone_cross_t.copy() + ImageDraw.Draw(branch_bottom_left).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_left).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_left).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + # generate the bottom texture + if data & 0b111111 == 0: + bottom = redstone_cross_t.copy() + + elif data & 0b1111 == 10: #= 0b1010 redstone wire in the x direction + bottom = redstone_wire_t.copy() + + elif data & 0b1111 == 5: #= 0b0101 redstone wire in the y direction + bottom = redstone_wire_t.copy().rotate(90) + + else: + bottom = Image.new("RGBA", (16,16), (38,92,255,0)) + if (data & 0b0001) == 1: + composite.alpha_over(bottom,branch_top_left) + + if (data & 0b1000) == 8: + composite.alpha_over(bottom,branch_top_right) + + if (data & 0b0010) == 2: + composite.alpha_over(bottom,branch_bottom_left) + + if (data & 0b0100) == 4: + composite.alpha_over(bottom,branch_bottom_right) + + # check for going up redstone wire + if data & 0b100000 == 32: + side1 = redstone_wire_t.rotate(90) + else: + side1 = None + + if data & 0b010000 == 16: + side2 = redstone_wire_t.rotate(90) + else: + side2 = None + + img = _build_full_block(None,side1,side2,None,None,bottom) + + return (img.convert("RGB"),img.split()[3]) + + + if blockID == 58: # crafting table + top = terrain_images[43] + side3 = terrain_images[43+16] + side4 = terrain_images[43+16+1] + + img = _build_full_block(top, None, None, side3, side4, None, 58) + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 59: # crops + raw_crop = terrain_images[88+data] + crop1 = transform_image(raw_crop, blockID) + crop2 = transform_image_side(raw_crop, blockID) + crop3 = crop2.transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, crop1, (0,12), crop1) + composite.alpha_over(img, crop2, (6,3), crop2) + composite.alpha_over(img, crop3, (6,3), crop3) + 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) + + 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) + return (img.convert("RGB"), img.split()[3]) + + + 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) + + 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) + return (img.convert("RGB"), img.split()[3]) + + + if blockID in (64,71): #wooden door, or iron door + if data & 0x8 == 0x8: # top of the door + raw_door = terrain_images[81 if blockID == 64 else 82] + else: # bottom of the door + raw_door = terrain_images[97 if blockID == 64 else 98] + + # if you want to render all doors as closed, then force + # force swung to be False + if data & 0x4 == 0x4: + swung=True + else: + swung=False + + # mask out the high bits to figure out the orientation + img = Image.new("RGBA", (24,24), (38,92,255,0)) + if (data & 0x03) == 0: + if not swung: + tex = transform_image_side(raw_door) + composite.alpha_over(img, tex, (0,6), tex) + else: + # flip first to set the doornob on the correct side + tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) + tex = tex.transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (0,0), tex) + + if (data & 0x03) == 1: + if not swung: + tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (0,0), tex) + else: + tex = transform_image_side(raw_door) + composite.alpha_over(img, tex, (12,0), tex) + + if (data & 0x03) == 2: + if not swung: + tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) + composite.alpha_over(img, tex, (12,0), tex) + else: + tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (12,6), tex) + + if (data & 0x03) == 3: + if not swung: + tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (12,6), tex) + else: + tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) + composite.alpha_over(img, tex, (0,6), tex) + + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 65: # ladder + raw_texture = terrain_images[83] + #print "ladder is facing: %d" % data + if data == 5: + # normally this ladder would be obsured by the block it's attached to + # but since ladders can apparently be placed on transparent blocks, we + # have to render this thing anyway. same for data == 2 + tex = transform_image_side(raw_texture) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, tex, (0,6), tex) + return (img.convert("RGB"), img.split()[3]) + if data == 2: + tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, tex, (12,6), tex) + return (img.convert("RGB"), img.split()[3]) + if data == 3: + tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, tex, (0,0), tex) + return (img.convert("RGB"), img.split()[3]) + if data == 4: + tex = transform_image_side(raw_texture) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, tex, (12,0), tex) + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 66: # minetrack: + + raw_straight = terrain_images[128] + raw_corner = terrain_images[112] + + ## use transform_image to scale and shear + if data == 0: + track = transform_image(raw_straight, blockID) + elif data == 6: + track = transform_image(raw_corner, blockID) + elif data == 7: + track = transform_image(raw_corner.rotate(270), blockID) + elif data == 8: + # flip + track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90), + blockID) + elif data == 9: + track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM), + blockID) + elif data == 1: + track = transform_image(raw_straight.rotate(90), blockID) + + #slopes + elif data == 2: # slope going up in +x direction + track = transform_image_slope(raw_straight,blockID) + track = track.transpose(Image.FLIP_LEFT_RIGHT) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, track, (2,0), track) + # the 2 pixels move is needed to fit with the adjacent tracks + return (img.convert("RGB"), img.split()[3]) + + elif data == 3: # slope going up in -x direction + # tracks are sprites, in this case we are seeing the "side" of + # the sprite, so draw a line to make it beautiful. + img = Image.new("RGBA", (24,24), (38,92,255,0)) + ImageDraw.Draw(img).line([(11,11),(23,17)],fill=(164,164,164)) + # grey from track texture (exterior grey). + # the track doesn't start from image corners, be carefull drawing the line! + return (img.convert("RGB"), img.split()[3]) + + elif data == 4: # slope going up in -y direction + track = transform_image_slope(raw_straight,blockID) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, track, (0,0), track) + return (img.convert("RGB"), img.split()[3]) + + elif data == 5: # slope going up in +y direction + # same as "data == 3" + img = Image.new("RGBA", (24,24), (38,92,255,0)) + ImageDraw.Draw(img).line([(1,17),(12,11)],fill=(164,164,164)) + return (img.convert("RGB"), img.split()[3]) + + + else: # just in case + track = transform_image(raw_straight, blockID) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, track, (0,12), track) + + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 85: # fences # create needed images for Big stick fence raw_texture = terrain_images[4] @@ -845,137 +1028,18 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"),img.split()[3]) - if blockID in (43,44): # slab and double-slab - - if data == 0: # stone slab - top = terrain_images[6] - side = terrain_images[5] - img = _build_block(top, side, blockID) - return (img.convert("RGB"), img.split()[3]) - - if data == 1: # stone slab - top = terrain_images[176] - side = terrain_images[192] - img = _build_block(top, side, blockID) - return (img.convert("RGB"), img.split()[3]) - - if data == 2: # wooden slab - top = side = terrain_images[4] - img = _build_block(top, side, blockID) - return (img.convert("RGB"), img.split()[3]) - - if data == 3: # cobblestone slab - top = side = terrain_images[16] - img = _build_block(top, side, blockID) - return (img.convert("RGB"), img.split()[3]) + if blockID in (86,91): # pumpkins, jack-o-lantern + top = transform_image(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) + img = Image.new("RGBA", (24,24), (38,92,255,0)) - if blockID == 9: # spring water, flowing water and waterfall water - - watertexture = _load_image("water.png") - - if (data & 0b10000) == 16: - top = watertexture - - else: top = None - - if (data & 0b0001) == 1: - side1 = watertexture # top left - else: side1 = None - - if (data & 0b1000) == 8: - side2 = watertexture # top right - else: side2 = None - - if (data & 0b0010) == 2: - side3 = watertexture # bottom left - else: side3 = None - - if (data & 0b0100) == 4: - side4 = watertexture # bottom right - else: side4 = None - - img = _build_full_block(top,side1,side2,side3,side4) - - return (img.convert("RGB"),img.split()[3]) - - - if blockID == 55: # redstone wire - - if data & 0b1000000 == 64: # powered redstone wire - redstone_wire_t = terrain_images[165] - redstone_wire_t = tintTexture(redstone_wire_t,(255,0,0)) - - redstone_cross_t = terrain_images[164] - redstone_cross_t = tintTexture(redstone_cross_t,(255,0,0)) - - - else: # unpowered redstone wire - redstone_wire_t = terrain_images[165] - redstone_wire_t = tintTexture(redstone_wire_t,(48,0,0)) - - redstone_cross_t = terrain_images[164] - redstone_cross_t = tintTexture(redstone_cross_t,(48,0,0)) - - # generate an image per redstone direction - branch_top_left = redstone_cross_t.copy() - ImageDraw.Draw(branch_top_left).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_top_left).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_top_left).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - branch_top_right = redstone_cross_t.copy() - ImageDraw.Draw(branch_top_right).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_top_right).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_top_right).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - branch_bottom_right = redstone_cross_t.copy() - ImageDraw.Draw(branch_bottom_right).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_bottom_right).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_bottom_right).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - branch_bottom_left = redstone_cross_t.copy() - ImageDraw.Draw(branch_bottom_left).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_bottom_left).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_bottom_left).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - # generate the bottom texture - if data & 0b111111 == 0: - bottom = redstone_cross_t.copy() - - elif data & 0b1111 == 10: #= 0b1010 redstone wire in the x direction - bottom = redstone_wire_t.copy() - - elif data & 0b1111 == 5: #= 0b0101 redstone wire in the y direction - bottom = redstone_wire_t.copy().rotate(90) - - else: - bottom = Image.new("RGBA", (16,16), (38,92,255,0)) - if (data & 0b0001) == 1: - composite.alpha_over(bottom,branch_top_left) - - if (data & 0b1000) == 8: - composite.alpha_over(bottom,branch_top_right) - - if (data & 0b0010) == 2: - composite.alpha_over(bottom,branch_bottom_left) - - if (data & 0b0100) == 4: - composite.alpha_over(bottom,branch_bottom_right) - - # check for going up redstone wire - if data & 0b100000 == 32: - side1 = redstone_wire_t.rotate(90) - else: - side1 = None - - if data & 0b010000 == 16: - side2 = redstone_wire_t.rotate(90) - else: - side2 = None - - img = _build_full_block(None,side1,side2,None,None,bottom) - - return (img.convert("RGB"),img.split()[3]) + 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]) if blockID == 92: # cake! (without bites, at the moment) @@ -1077,43 +1141,52 @@ def getBiomeData(worlddir, chunkX, chunkY): # This set holds block ids that require special pre-computing. These are typically # things that require ancillary data to render properly (i.e. ladder plus orientation) +# A good source of information is: +# http://www.minecraftwiki.net/wiki/Data_values +# (when adding new blocks here and in generate_special_textures, +# please, if possible, keep the ascending order of blockid value) -special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18,85,17,23,35,51,43,44,9,55,58,92]) +special_blocks = set([2, 9, 17, 18, 23, 35, 43, 44, 50, 51, 55, 58, 59, \ + 61, 62, 64, 65, 66, 71, 75, 76, 85, 86, 91, 92]) # 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[66] = range(10) # minecrart tracks -special_map[59] = range(8) # crops -special_map[61] = range(6) # furnace -special_map[62] = range(6) # burning furnace -special_map[65] = (2,3,4,5) # ladder -special_map[64] = range(16) # wooden door -special_map[71] = range(16) # iron door -special_map[91] = range(5) # jack-o-lantern -special_map[86] = range(5) # pumpkin -special_map[85] = range(17) # fences + +special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values. special_map[17] = range(4) # wood: normal, birch and pine -special_map[23] = range(6) # dispensers +special_map[23] = range(6) # dispensers, orientation special_map[35] = range(16) # wool, colored and white -special_map[51] = range(16) # fire 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[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values. -special_map[55] = range(128) # redstone wire -special_map[58] = (0,) # crafting table +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[55] = range(128) # redstone wire, all the possible combinations +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[64] = range(16) # wooden door, open/close and orientation +special_map[65] = (2,3,4,5) # ladder, orientation (not implemented) +special_map[66] = range(10) # minecrart tracks, orientation, slope +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[92] = range(6) # cake! -# apparently pumpkins and jack-o-lanterns have ancillary data, but it's unknown -# what that data represents. For now, assume that the range for data is 0 to 5 -# like torches -special_map[2] = (0,) # grass -special_map[18] = range(16) # leaves -# grass and leaves are now graysacle in terrain.png +# 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 +special_map[18] = range(16) # leaves, birch, normal or pine leaves (not implemented) -# leaves have ancilary data, but its meaning is unknown (age perhaps?) specialblockmap = {} diff --git a/overviewer_core/util.py b/overviewer_core/util.py index a1d07f8..2e0bdbd 100644 --- a/overviewer_core/util.py +++ b/overviewer_core/util.py @@ -36,11 +36,14 @@ def get_program_path(): def findGitVersion(): - if os.path.exists(".git"): - with open(os.path.join(".git","HEAD")) as f: + this_dir = get_program_path() + if os.path.exists(os.path.join(this_dir,".git")): + with open(os.path.join(this_dir,".git","HEAD")) as f: data = f.read().strip() if data.startswith("ref: "): - with open(os.path.join(".git", data[5:])) as g: + if not os.path.exists(os.path.join(this_dir,data[5:])): + return data + with open(os.path.join(this_dir, ".git", data[5:])) as g: return g.read().strip() else: return data