From f07cf1e0d86cc1e4b4d4bff2700eb4c1ef4b8922 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 26 Oct 2011 21:19:53 -0400 Subject: [PATCH 01/30] imporoved doc page cross-referencing --- docs/building.rst | 14 +++++++++----- docs/index.rst | 8 +++++--- docs/installing.rst | 8 ++++---- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/docs/building.rst b/docs/building.rst index 21f4371..f62cb8b 100644 --- a/docs/building.rst +++ b/docs/building.rst @@ -2,11 +2,15 @@ Building the Overviewer from Source =================================== -These instructions are for building the C extension for Overviewer. Also note -that pre-built Windows and Debian executables are available in the `Downloads -`_ section. These -kits already contain the compiled extension and require no further setup (so you -can skip to the :doc:`Running ` section of the docs). +These instructions are for building the C extension for Overviewer. Once you +have finished with these instructions, head to :doc:`running`. + +.. note:: + + Pre-built Windows and Debian executables are available on the + :doc:`installing` page. These kits already contain the compiled code and + require no further setup, so you can skip to the next section of the docs: + :doc:`running`. Get The Source ============== diff --git a/docs/index.rst b/docs/index.rst index 57b1663..c990364 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -54,8 +54,10 @@ Windows, Mac, and Linux as long as you have these software packages installed: * Either a Minecraft Client installed or a terrain.png for the textures. -There are additional requirements for compiling it. More details are available -in either the :doc:`Building ` or :doc:`Installing ` pages. +The first three are included in the Windows binary download. Also, there are +additional requirements for compiling it (like a compiler). More details are +available in either the :doc:`Building ` or :doc:`Installing +` pages. Getting Started =============== @@ -92,8 +94,8 @@ Documentation Contents .. toctree:: :maxdepth: 2 - building installing + building running options faq diff --git a/docs/installing.rst b/docs/installing.rst index a9b5b4b..aeb41cd 100644 --- a/docs/installing.rst +++ b/docs/installing.rst @@ -3,8 +3,9 @@ Installing ========== This page is for installing the pre-compiled binary versions of the Overviewer. -If you have built the Overviewer from source yourself, head back to -:doc:`Building `. +If you want to build the Overviewer from source yourself, head to :doc:`Building +`. If you have already built The Overviewer, proceed to +:doc:`running`. Windows @@ -18,8 +19,7 @@ right place! For 64 bit, you'll want these instead: `VC++ 2008 `_ and `VC++ 2010 `_ -3. That's it! Proceed with instructions on :doc:`Running ` the - Overviewer. +3. That's it! Proceed with instructions on :doc:`running`. Debian / Ubuntu =============== From a470ff50d6225d3343e3aac8f601848f669a5dde Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 26 Oct 2011 21:34:46 -0400 Subject: [PATCH 02/30] added question headers to faq site and added an faq on windows cmd --- docs/faq.rst | 61 +++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/docs/faq.rst b/docs/faq.rst index f9790a1..213d1d9 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -2,22 +2,53 @@ Frequently Asked Questions ========================== -**The full map doesn't display even when fully zoomed out!** - Are you using the :option:`-z` or :option:`--zoom <-z>` option on your commandline or - in settings.py? If so, try removing it, or increasing the value you set. - It's quite likely you don't need it at all. See the documentation for the - :option:`zoom <-z>` option. +.. contents:: + :local: -**You've added a few feature, but it's not showing up on my map!** - Some new features will only show up in newly-rendered areas. Use the - :option:`--forcerender` option to update the entire map. +General Questions +================= -**How do I use this on CentOS 5?** - CentOS 5 comes with Python 2.4, but the Overviewer needs 2.6 or higher. See - the special instructions at :ref:`centos` +The full map doesn't display even when fully zoomed out! +-------------------------------------------------------- -**The background color of the map is black, and I don't like it!** - You can change this by using the :option:`--bg-color` command line option, or - ``bg_color`` in settings.py. See the :doc:`options` page for more - details. +Are you using the :option:`-z` or :option:`--zoom <-z>` option on your +commandline or in settings.py? If so, try removing it, or increasing the value +you set. It's quite likely you don't need it at all. See the documentation for +the :option:`zoom <-z>` option. +You've added a few feature, but it's not showing up on my map! +-------------------------------------------------------------- + +Some new features will only show up in newly-rendered areas. Use the +:option:`--forcerender` option to update the entire map. + +How do I use this on CentOS 5? +------------------------------ + +CentOS 5 comes with Python 2.4, but the Overviewer needs 2.6 or higher. See the +special instructions at :ref:`centos` + +The background color of the map is black, and I don't like it! +-------------------------------------------------------------- + +You can change this by using the :option:`--bg-color` command line option, or +``bg_color`` in settings.py. See the :doc:`options` page for more details. + +I downloaded the Windows version but when I double-click it, the window closes real fast. +----------------------------------------------------------------------------------------- + +The Overviewer is a command line program and must be run from a command line. It +is necessary to become at least a little familiar with a command line to run The +Overviewer (if you have no interest in this, perhaps this isn't the mapping +program for you). + +Unfortunately, A full tutorial of the Windows command line is out of scope for this +documentation; consult the almighty Google for tutorials and information on +the Windows command line. (If you would like to contribute a short tutorial to +these docs, please do!) + +Batch files are another easy way to run the Overviewer without messing with +command lines, but information on how to do this has also not been written. + +On a related note, we also welcome contributions for a graphical interface for +the Overviewer. From b31160e3ade6277b77363cad28a2354200333337 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 26 Oct 2011 22:00:25 -0400 Subject: [PATCH 03/30] more minor clarifications --- docs/building.rst | 18 ++++++++++++++---- docs/index.rst | 15 ++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/docs/building.rst b/docs/building.rst index f62cb8b..9dbfe27 100644 --- a/docs/building.rst +++ b/docs/building.rst @@ -14,13 +14,18 @@ have finished with these instructions, head to :doc:`running`. Get The Source ============== -First step: download the source! Either clone with Git or download the most recent snapshot -* URL to clone: ``git://github.com/overviewer/Minecraft-Overviewer.git`` +First step: download the platform-independent source! Either clone with Git +(recommended if you know Git) or download the most recent snapshot + +* Git URL to clone: ``git://github.com/overviewer/Minecraft-Overviewer.git`` * `Download most recent tar archive `_ * `Download most recent zip archive `_ +Once you have the source, see below for instructions on building for your +system. + Build Instructions For Various Operating Systems ================================================ @@ -161,5 +166,10 @@ regular user. Installing the Compiled Code ---------------------------- -You can run the ``overviewer.py`` script from the build directory just fine. If -you'd like to install, run ``python setup.py install`` + +You can run the ``overviewer.py`` script from the build directory just fine; +installation is unnecessary. If you'd like to install, run + +:: + + python setup.py install diff --git a/docs/index.rst b/docs/index.rst index c990364..b471a14 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,6 +6,10 @@ The Minecraft Overviewer ======================== +See also the `Github Homepage`_ + +.. _Github Homepage: https://github.com/overviewer/Minecraft-Overviewer + Introduction ============ The Minecraft Overviewer is a command-line tool for rendering high-resolution @@ -54,10 +58,9 @@ Windows, Mac, and Linux as long as you have these software packages installed: * Either a Minecraft Client installed or a terrain.png for the textures. -The first three are included in the Windows binary download. Also, there are -additional requirements for compiling it (like a compiler). More details are -available in either the :doc:`Building ` or :doc:`Installing -` pages. +The first three are included in the Windows download. Also, there are additional +requirements for compiling it (like a compiler). More details are available in +either the :doc:`Building ` or :doc:`Installing ` pages. Getting Started =============== @@ -73,7 +76,9 @@ packages and don't want to have to compile anything yourself**, head to the **If you would like to build the Overviewer from source yourself (it's not that bad)**, head to the :doc:`Building ` page. -.. _Github Homepage: https://github.com/overviewer/Minecraft-Overviewer +**For all other platforms** you will need to build it yourself. +:doc:`building`. + Help ==== From f5ae4b34511a26cc864259b5cc0adcb3b936b550 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 26 Oct 2011 22:36:23 -0400 Subject: [PATCH 04/30] changed bare exceptions to except Exception --- overviewer.py | 2 +- overviewer_core/textures.py | 4 ++-- overviewer_core/util.py | 6 +++--- setup.py | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/overviewer.py b/overviewer.py index 3399424..9f58ed0 100755 --- a/overviewer.py +++ b/overviewer.py @@ -139,7 +139,7 @@ def main(): print "Git commit: %s" % overviewer_version.HASH print "built on %s" % overviewer_version.BUILD_DATE print "Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM, overviewer_version.BUILD_OS) - except: + except Exception: print "version info not found" pass sys.exit(0) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 48a2f20..c7e58d8 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -2255,7 +2255,7 @@ def prepareBiomeData(worlddir): try: grasscolor = list(Image.open(os.path.join(biomeDir,"grasscolor.png")).getdata()) foliagecolor = list(Image.open(os.path.join(biomeDir,"foliagecolor.png")).getdata()) - except: + except Exception: # clear anything that managed to get set grasscolor = None foliagecolor = None @@ -2316,7 +2316,7 @@ def loadLightColor(): lightcolor_checked = True try: lightcolor = list(_load_image("light_normal.png").getdata()) - except: + except Exception: logging.warning("Light color image could not be found.") lightcolor = None return lightcolor diff --git a/overviewer_core/util.py b/overviewer_core/util.py index d6c8959..2e51a55 100644 --- a/overviewer_core/util.py +++ b/overviewer_core/util.py @@ -52,7 +52,7 @@ def findGitHash(): try: import overviewer_version return overviewer_version.HASH - except: + except Exception: return "unknown" def findGitVersion(): @@ -68,9 +68,9 @@ def findGitVersion(): # and 0.1.3 into 0.1.3 line = '-'.join(line.split('-', 2)[:2]) return line.strip() - except: + except Exception: try: import overviewer_version return overviewer_version.VERSION - except: + except Exception: return "unknown" diff --git a/setup.py b/setup.py index cd1b7d0..2cfe65a 100755 --- a/setup.py +++ b/setup.py @@ -142,7 +142,7 @@ except AttributeError: try: pil_include = os.environ['PIL_INCLUDE_DIR'].split(os.pathsep) -except: +except Exception: pil_include = [ os.path.join(get_python_inc(plat_specific=1), 'Imaging') ] if not os.path.exists(pil_include[0]): pil_include = [ ] @@ -221,7 +221,7 @@ def generate_version_py(): f = open("overviewer_core/overviewer_version.py", "w") f.write(outstr) f.close() - except: + except Exception: print "WARNING: failed to build overviewer_version file" class CustomSDist(sdist): From 4327eef475c58c1daf33d99bbc632ca0d38cbd36 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 26 Oct 2011 22:34:34 -0400 Subject: [PATCH 05/30] Provide a useful message if overviewer is doubleclicked. Works for both py2exe .exes as well as double clicking the overviewer.py file --- overviewer.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/overviewer.py b/overviewer.py index 9f58ed0..6992fdc 100755 --- a/overviewer.py +++ b/overviewer.py @@ -15,7 +15,25 @@ # You should have received a copy of the GNU General Public License along # with the Overviewer. If not, see . +import platform import sys + +if platform.system() == 'Windows': + try: + import ctypes + GetConsoleProcessList = ctypes.windll.kernel32.GetConsoleProcessList + num = GetConsoleProcessList(ctypes.byref(ctypes.c_int(0)), ctypes.c_int(1)) + if (num == 1): + print "The Overviewer is a console program. Please open a Windows command prompt" + print "first and run Overviewer from there. Further documentation is available at" + print "http://docs.overviewer.org/\n" + print "Press [Enter] to close this window." + raw_input() + sys.exit(1) + + except Exception: + pass + if not (sys.version_info[0] == 2 and sys.version_info[1] >= 6): print "Sorry, the Overviewer requires at least Python 2.6 to run" if sys.version_info[0] >= 3: @@ -29,7 +47,6 @@ 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") From 02c30c6f8e3c44267f8d902e4047d5c9ba0e6767 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 26 Oct 2011 23:06:52 -0400 Subject: [PATCH 06/30] Update URL hash on double-click --- overviewer_core/data/web_assets/overviewer.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/overviewer_core/data/web_assets/overviewer.js b/overviewer_core/data/web_assets/overviewer.js index 34fbfe8..02fa8d9 100644 --- a/overviewer_core/data/web_assets/overviewer.js +++ b/overviewer_core/data/web_assets/overviewer.js @@ -261,6 +261,9 @@ var overviewer = { google.maps.event.addListener(overviewer.map, 'zoom_changed', function() { overviewer.util.updateHash(); }); + google.maps.event.addListener(overviewer.map, 'dblclick', function() { + overviewer.util.updateHash(); + }); // Make the link again whenever the map changes google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function() { From bc9d368ae8b67f5bbaf8d140f31203d27c1bfd6c Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Thu, 27 Oct 2011 01:02:36 -0400 Subject: [PATCH 07/30] added section about biome support to docs --- docs/running.rst | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/docs/running.rst b/docs/running.rst index e5d873c..adc93c7 100644 --- a/docs/running.rst +++ b/docs/running.rst @@ -137,3 +137,23 @@ modification times intact, use ``cp -p``. For people who render from backups, GNU ``tar`` automatically handles modification times correctly. ``rsync -a`` will handle this correctly as well. If you use some other tool, you'll have to figure out how to do this yourself. + +Biome Support +============= + +Minecraft Overviewer has support for using the biome info from the `Minecraft +Biome Extractor`_. If you run the biome extractor on your world, during the +next run Overviewer will automatically recognize the biome info and use it to +colorize your grass and leaves appropriately. This will only appear on updated +chunks, though; to colorize the entire world you will need to re-render from +scratch by using :option:`--forcerender` + +.. note:: + + as of Minecraft 1.8, you currently need to use a patched Biome Extractor + that can be found `here + `_, + or `here on GitHub + `_. + +.. _Minecraft Biome Extractor: http://www.minecraftforum.net/viewtopic.php?f=25&t=80902 From a3653d429f11ff7b0f088e6947828eeafb2b74eb Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 30 Oct 2011 00:08:48 -0400 Subject: [PATCH 08/30] fixed typo in configParser.py when parsing float values --- overviewer_core/configParser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overviewer_core/configParser.py b/overviewer_core/configParser.py index ff685ba..795fd54 100644 --- a/overviewer_core/configParser.py +++ b/overviewer_core/configParser.py @@ -196,7 +196,7 @@ class ConfigOptionParser(object): sys.exit(1) return value elif a['type'] == "float": - return long(value) + return float(value) elif a['type'] == "complex": return complex(value) elif a['type'] == "function": From 3a0d859b73d4886c16863e5eabdbb75f3dd93d16 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sun, 30 Oct 2011 00:58:10 -0400 Subject: [PATCH 09/30] moved block rendering images to own folder in docs --- docs/design/{ => blockrendering}/cube_parts.png | Bin docs/design/{ => blockrendering}/cube_parts.svg | 0 docs/design/{ => blockrendering}/cube_sides.png | Bin docs/design/{ => blockrendering}/cube_top.png | Bin docs/design/{ => blockrendering}/pixelfix.png | Bin docs/design/{ => blockrendering}/pixelfix.svg | 0 .../{ => blockrendering}/tessellation.png | Bin .../{ => blockrendering}/tessellation.svg | 0 .../{ => blockrendering}/texturecubing.png | Bin .../{ => blockrendering}/texturecubing.svg | 0 .../{ => blockrendering}/texturesidesteps.png | Bin .../{ => blockrendering}/texturesidesteps.svg | 0 .../{ => blockrendering}/texturetopsteps.png | Bin .../{ => blockrendering}/texturetopsteps.svg | 0 docs/design/designdoc.rst | 16 ++++++++-------- 15 files changed, 8 insertions(+), 8 deletions(-) rename docs/design/{ => blockrendering}/cube_parts.png (100%) rename docs/design/{ => blockrendering}/cube_parts.svg (100%) rename docs/design/{ => blockrendering}/cube_sides.png (100%) rename docs/design/{ => blockrendering}/cube_top.png (100%) rename docs/design/{ => blockrendering}/pixelfix.png (100%) rename docs/design/{ => blockrendering}/pixelfix.svg (100%) rename docs/design/{ => blockrendering}/tessellation.png (100%) rename docs/design/{ => blockrendering}/tessellation.svg (100%) rename docs/design/{ => blockrendering}/texturecubing.png (100%) rename docs/design/{ => blockrendering}/texturecubing.svg (100%) rename docs/design/{ => blockrendering}/texturesidesteps.png (100%) rename docs/design/{ => blockrendering}/texturesidesteps.svg (100%) rename docs/design/{ => blockrendering}/texturetopsteps.png (100%) rename docs/design/{ => blockrendering}/texturetopsteps.svg (100%) diff --git a/docs/design/cube_parts.png b/docs/design/blockrendering/cube_parts.png similarity index 100% rename from docs/design/cube_parts.png rename to docs/design/blockrendering/cube_parts.png diff --git a/docs/design/cube_parts.svg b/docs/design/blockrendering/cube_parts.svg similarity index 100% rename from docs/design/cube_parts.svg rename to docs/design/blockrendering/cube_parts.svg diff --git a/docs/design/cube_sides.png b/docs/design/blockrendering/cube_sides.png similarity index 100% rename from docs/design/cube_sides.png rename to docs/design/blockrendering/cube_sides.png diff --git a/docs/design/cube_top.png b/docs/design/blockrendering/cube_top.png similarity index 100% rename from docs/design/cube_top.png rename to docs/design/blockrendering/cube_top.png diff --git a/docs/design/pixelfix.png b/docs/design/blockrendering/pixelfix.png similarity index 100% rename from docs/design/pixelfix.png rename to docs/design/blockrendering/pixelfix.png diff --git a/docs/design/pixelfix.svg b/docs/design/blockrendering/pixelfix.svg similarity index 100% rename from docs/design/pixelfix.svg rename to docs/design/blockrendering/pixelfix.svg diff --git a/docs/design/tessellation.png b/docs/design/blockrendering/tessellation.png similarity index 100% rename from docs/design/tessellation.png rename to docs/design/blockrendering/tessellation.png diff --git a/docs/design/tessellation.svg b/docs/design/blockrendering/tessellation.svg similarity index 100% rename from docs/design/tessellation.svg rename to docs/design/blockrendering/tessellation.svg diff --git a/docs/design/texturecubing.png b/docs/design/blockrendering/texturecubing.png similarity index 100% rename from docs/design/texturecubing.png rename to docs/design/blockrendering/texturecubing.png diff --git a/docs/design/texturecubing.svg b/docs/design/blockrendering/texturecubing.svg similarity index 100% rename from docs/design/texturecubing.svg rename to docs/design/blockrendering/texturecubing.svg diff --git a/docs/design/texturesidesteps.png b/docs/design/blockrendering/texturesidesteps.png similarity index 100% rename from docs/design/texturesidesteps.png rename to docs/design/blockrendering/texturesidesteps.png diff --git a/docs/design/texturesidesteps.svg b/docs/design/blockrendering/texturesidesteps.svg similarity index 100% rename from docs/design/texturesidesteps.svg rename to docs/design/blockrendering/texturesidesteps.svg diff --git a/docs/design/texturetopsteps.png b/docs/design/blockrendering/texturetopsteps.png similarity index 100% rename from docs/design/texturetopsteps.png rename to docs/design/blockrendering/texturetopsteps.png diff --git a/docs/design/texturetopsteps.svg b/docs/design/blockrendering/texturetopsteps.svg similarity index 100% rename from docs/design/texturetopsteps.svg rename to docs/design/blockrendering/texturetopsteps.svg diff --git a/docs/design/designdoc.rst b/docs/design/designdoc.rst index e6a7ef7..a97b182 100644 --- a/docs/design/designdoc.rst +++ b/docs/design/designdoc.rst @@ -90,7 +90,7 @@ the process remains the same). In order to render a cube out of this, an `affine transformation`_ is applied to the texture in order to transform it to the top, left, and right faces of the cube. -.. image:: texturecubing.png +.. image:: blockrendering/texturecubing.png :alt: A texture gets rendered into a cube .. _affine transformation: http://en.wikipedia.org/wiki/Affine_transformation @@ -130,12 +130,12 @@ these steps: This produces an image of size 24 by 12 as seen in the following sequence. -.. image:: texturetopsteps.png +.. image:: blockrendering/texturetopsteps.png :alt: The 4 steps for transforming a texture square into the top of the cube. The final image, shown below, becomes the top of the cube. -.. image:: cube_top.png +.. image:: blockrendering/cube_top.png :alt: Top of the cube On the left is what will become the top of the block at actual size after the @@ -156,13 +156,13 @@ a shear. 2. The 12 by 12 square is sheared by a factor of 1.5 in the Y direction, producing an image that is bounded by a 12 by 18 pixel square. -.. image:: texturesidesteps.png +.. image:: blockrendering/texturesidesteps.png :alt: Texture being sheared for the side of the cube. This image is simply flipped along the horizontal axis for the other visible side of the cube. -.. image:: cube_sides.png +.. image:: blockrendering/cube_sides.png :alt: The sides of the block Again, shown on the left are the two sides of the block at actual size, the @@ -177,7 +177,7 @@ However, notice from the middle of the three images in the sequence below that the images as transformed don't fit together exactly. There is some overlap when put in the 24 by 24 box in which they must fit. -.. image:: cube_parts.png +.. image:: blockrendering/cube_parts.png :alt: How the cube parts fit together There is one more complication. The cubes don't tessellate perfectly. This @@ -185,7 +185,7 @@ diagram illustrates when a cube is positioned next to another. The lower cubes are 18 pixels lower and 12 pixels to either side, which is half the width and 3/4 the height respectively. -.. image:: tessellation.png +.. image:: blockrendering/tessellation.png :alt: Cubes don't tessellate perfectly The solution is to manually touch up those 6 pixels. 3 pixels are added on the @@ -194,7 +194,7 @@ perfectly! This is done at the end of :func:`textures._build_block` -.. image:: pixelfix.png +.. image:: blockrendering/pixelfix.png :alt: The 6 pixels manually added to each cube. Other Cube Types From 3917259e33afa54c23ea47d9371031dbeed67e4c Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Sun, 30 Oct 2011 03:00:28 -0400 Subject: [PATCH 10/30] wrote more design docs on chunk positioning in tiles --- docs/design/designdoc.rst | 122 + docs/design/tilerendering/chunkgrid.png | Bin 0 -> 34249 bytes docs/design/tilerendering/chunkgrid.svg | 529 ++ .../tilerendering/chunkgridwithrowcol.png | Bin 0 -> 54803 bytes .../tilerendering/chunkgridwithrowcol.svg | 786 +++ .../design/tilerendering/chunkpositioning.png | Bin 0 -> 20599 bytes .../design/tilerendering/chunkpositioning.svg | 366 ++ docs/design/tilerendering/chunksintile.png | Bin 0 -> 27189 bytes docs/design/tilerendering/chunksintile.svg | 326 ++ docs/design/tilerendering/topofchunk.png | Bin 0 -> 48432 bytes docs/design/tilerendering/topofchunk.svg | 4258 +++++++++++++++++ 11 files changed, 6387 insertions(+) create mode 100644 docs/design/tilerendering/chunkgrid.png create mode 100644 docs/design/tilerendering/chunkgrid.svg create mode 100644 docs/design/tilerendering/chunkgridwithrowcol.png create mode 100644 docs/design/tilerendering/chunkgridwithrowcol.svg create mode 100644 docs/design/tilerendering/chunkpositioning.png create mode 100644 docs/design/tilerendering/chunkpositioning.svg create mode 100644 docs/design/tilerendering/chunksintile.png create mode 100644 docs/design/tilerendering/chunksintile.svg create mode 100644 docs/design/tilerendering/topofchunk.png create mode 100644 docs/design/tilerendering/topofchunk.svg diff --git a/docs/design/designdoc.rst b/docs/design/designdoc.rst index a97b182..357ecd7 100644 --- a/docs/design/designdoc.rst +++ b/docs/design/designdoc.rst @@ -336,6 +336,128 @@ Tile Rendering ============== .. Covers the placement of chunk images on a tile +So now that we know how to draw a single chunk, we can move on to placing them +on an image. + +For the diagrams in this section, we are positioning an entire chunk, but +frequently, only the top face of the chunk is drawn (shown in green below). + +.. image:: tilerendering/topofchunk.png + :alt: The top of a chunk is highlighted + +This makes it easier and less cumbersome to describe chunk positionings. Just +remember that chunks extend down for 1536 more pixels. + +Chunk Addressing +---------------- + +Chunks in Minecraft have an X,Z address, starting at 0,0 and extending to +positive and negative infinity on both axes. Since we're looking at things +diagonally, however, we need a way of addressing these chunks in the final +image. For that, we refer to them in rows and columns. Consider this grid +showing the tops of a five by five region of chunks, labeled with their in-game +addresses. + +.. image:: tilerendering/chunkgrid.png + :alt: A grid of 5x5 chunks showing how chunks are addressed. + +Now, we want to transform each chunk to a row/column address as shown here: + +.. image:: tilerendering/chunkgridwithrowcol.png + :alt: A grid of 5x5 chunks showing how chunks are addressed. + +So the chunk at address 0,0 would be at col 0, row 0; while the chunk at address +1,1 would be at col 2, row 0. The intersection of the red and green lines +addresses the chunk in col,row format. + +Notice that as a consequence of this addressing scheme, there is no chunk at +e.g. column 1 row 0. There are some col,row addresses that lie between chunks +(as can be seen where the red/green lines intersect at a chunk boundary instead +of the middle of a chunk). Something to keep in mind. + +So how does one translate between them? It turns out that a chunk's column +address is simply the sum of the X and the Z coordinate, while the row is the +difference. Try it! + +:: + + col = X + Z + row = Z - X + + X = (col - row) / 2 + Z = (col + row) / 2 + +Chunk Positioning +----------------- +Again just looking at the top of a chunk, we can work out how to position them +relative to each other. This is similar to how to position blocks relative to +each other, but this time, for chunks. + +A chunk's top face is 384 pixels wide by 192 pixels tall. Similar to the block, +neighboring chunks have these relationships: + +.. image:: tilerendering/chunkpositioning.png + :alt: Chunk positioning diagram + +But that's all pretty trivial. With this knowledge, we could draw the chunks at +the above offsets in one large image, but for large worlds, that would quickly +become too much to handle. (Early versions of the Overviewer did this, but the +large, unwieldy images quickly motivated the development of rendering to +individual tiles) + +Tile Layout +----------- + +Instead of rendering to one large image, chunks are rendered to small tiles. +Only a handful of chunks need to be rendered into each tile. The downside is +that chunks must be rendered multiple times for each tile they appear in, but +the upside is that arbitrarily sized maps can be viewed. + +The Overviewer uses a tile size of 384 by 384 pixels. This is the same as a +width of a chunk and is no coincidence. Just considering the top face of a +chunk, 8 chunks get rendered into a tile in this configuration: + +.. image:: tilerendering/chunksintile.png + :alt: The 8 chunks that get rendered into a tile + +So the overall strategy is to convert all chunks into diagonal col,row +coordinates, then for each tile decide which chunks belong in it, then render +them in the appropriate place on the tile. + +The rendering routines are actually passed a range of chunks to render, e.g. +rows 4-6, cols 20-24. The lower bound col,row chunk given in the range is +rendered at position 0,0 in the diagram above. That is, at offset -192,-96 +pixels. + +The rendering routines takes the given range of columns and rows, converts it +back into chunk coordinates, and renders the given 8 chunks plus all chunks from +the 16 rows above the given range (see the note below). The chunks are +positioned correctly with the above positioning rules, so any chunks that are +out of the bounds get rendered off the tile and don't affect the final image. +(There is therefore no penalty for rendering out-of-bounds chunks for a tile +except increased processing) + +.. note:: + + Remember that chunks are actually very tall, so there are actually several + rows above 0 in the above diagram that are rendered into the tile. Since the + chunk outlines in the diagrams are only the top face of the chunk, they most + likely don't contribute to the image since chunks usually don't have + anything to render way up at the top near the sky. + +Since every other column of chunks is half-way in two tiles, they must be +rendered twice. Each neighboring tile is therefore only 2 columns over, not 3 as +one may suspect at first. Same goes for the rows: The next tile down is 4 rows +down, not 5. + +Quadtrees +========= +.. About the tile output + +get_range_by_path +----------------- +.. Explain the quadtree.QuadtreeGen._get_range_by_path method + Reading the Data Files ====================== .. diff --git a/docs/design/tilerendering/chunkgrid.png b/docs/design/tilerendering/chunkgrid.png new file mode 100644 index 0000000000000000000000000000000000000000..53f793d912520042d4cb9efede72762ab34343ea GIT binary patch literal 34249 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 PFfe$!`njxgN@xNA + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + 0,0 + 1,1 + 2,2 + -1,-1 + -2,-2 + 1,-1 + 2,-2 + -1,1 + -2,2 + 0,1 + 0,2 + -1,2 + 1,0 + 2,-1 + 2,0 + 2,1 + 1,2 + -2,1 + -1,0 + -2,0 + 0,-1 + 0,-2 + 1,-2 + -1,-2 + -2,-1 + X + Z + + + + diff --git a/docs/design/tilerendering/chunkgridwithrowcol.png b/docs/design/tilerendering/chunkgridwithrowcol.png new file mode 100644 index 0000000000000000000000000000000000000000..6f2372513efc9d6c1667f1e3906a834ed8ecdcb2 GIT binary patch literal 54803 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

(BZ>XT7!H-Lis*d}l2Fzy1=pYi{lR`+xjytH(># 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 literal 0 HcmV?d00001 diff --git a/docs/design/tilerendering/chunkgridwithrowcol.svg b/docs/design/tilerendering/chunkgridwithrowcol.svg new file mode 100644 index 0000000..0b94f76 --- /dev/null +++ b/docs/design/tilerendering/chunkgridwithrowcol.svg @@ -0,0 +1,786 @@ + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + 0,0 + + 1,1 + + 2,2 + + -1,-1 + + -2,-2 + + 1,-1 + + 2,-2 + -1,1 + -2,2 + 0,1 + 0,2 + -1,2 + 1,0 + 2,-1 + 2,0 + 2,1 + 1,2 + -2,1 + -1,0 + + -2,0 + + 0,-1 + + 1,-2 + + -1,-2 + + -2,-1 + Col 0 + Col 1 + Col 2 + Col 3 + Col 4 + Col -4 + Col -3 + Col -2 + Col -1 + Row 0 + Row 1 + Row 2 + Row 3 + Row 4 + + Row -1 + + Row -2 + + Row -3 + Row -4 + 0,-2 + + diff --git a/docs/design/tilerendering/chunkpositioning.png b/docs/design/tilerendering/chunkpositioning.png new file mode 100644 index 0000000000000000000000000000000000000000..da21b395ba4310331eeb8d5aceaba5e5fb85f4df GIT binary patch literal 20599 zcmeAS@N?(olHy`uVBq!ia0y~yVEVmmtT}V z`<;yx1A_vCr;B4q#hf>DOJ~T0ervn0U*S>EuG+*}Ue5n++XjI0RE{n7k zR!SSlZ{XNqU|?C$`qui`rvFU`7!Sl8SbpH|ftB0-Yus4QzQJyTo`LiQ-U@b$%iolz z?w;kMG|@vvsB_xOY1onWUNQ*iwv9Lsk5 z{_TB7za3xu<`(-5&K-+So;<%p?uY1&l@Dg7tuS=58ESW$5TJ^nV&F}c) zuba7NExnzNs651T?}Spw&SCx>cPeBCU58Z&C&s#|=kOby}+k;y^jLY+b$v$QT>&i_^G z<>dT7olBgk@jQ+7>$k;TKRCVBdQ zZTN@xuRGhqiy7}Txiep%ma*-tML`Jnb8o9(5N{MIx_%Mvl$m&iS%Rg8Atv$n={de$ zV3RH?sJPn)PEQG%_V`Jz`^*_~I|OG)NHFfnAz>y4g(Y{_~W1SN(xlWaV_~qW=};z4w~gH-0~PdqR;yrnl3{=Za3N z|DTF~U~n)}Mcp0}5A2?ObxISJC#kX3G5zWl+c~{xvnE6)Il$cN*Q9OJwxrxEQ?cq> z5~=!j>UP?$5YX3<>Tjvv(DK;gtOxS1CR5g-9@?_tRWx=(>b2m?!ghm*b_D zNz102#7Tdfx+L?~rhG_7b5Xh&HfeFZn~JQ~OIxMqzl0yCd-{7vZ9THj*mD=#55XTg zx4#Q|c5f}}tM~d9aw&M~jy9OF$rHK?PoGvfP_5m{Q<+75Du;H|%!2SbAIKulNq( z8!{DY)wOvEjt}g&@t=s>?SDz*K~93{gGq;&Jzq`RTc&$K{71B_9?b25g({`&X?lkj z?%Vozf6)JlGJj8fuKM=5z3KX_f3|xv?k~IVt-0Y1=NYaw+;>9EUPD4kMX1uix}Zw? z_ZL<>rn=n3`MVn*d;fJW`l8*U!uXkqxzpy)>e>I!e=G3pXS(gV>)f~ONofqv)?6}J$(aqU~Um(*)W1i#|&>}ER6EPnH!T7GpF<86H=?JZmSm$Wx9ZgVetBJ=gh zi*n{F&nSg_XRi-i-+rH6H}MOnf#!v|FG_Bm|JLsMK5}Zoz4)`==F9D1pK<$}KkIR! z0z-o{-!5#FRLHDKxMdXUzjOw#gnGU*#PK2Qp56@Z4B09Ayi-cl3TC}s|7r7@>;V71 ze-#JrH;R5XTf)L`_-oS1gao5Q*HRmcRo~|Pe%w@R#=4E+o|4j!ZsyaB)gD&I$}S6? z)pOx*+!?iTq5XzP_4SJ{`LHdqf90}B*5j7QkE`F#R^FcULSoV~)^D3@6YgzO5xVMh z$*84yzt#uK0)YbiiF*q)Hf&$AxiL7We&XzfyneQpzxFd6zqwB>-#O~=x0^TZ*}bFq zDkN@5TL?H@I&(lEq2uk_S@+a4-5)SHZGGORGxs^mg9VE`omLjdam7SE^VZb9xu$OZ zp0^$CT}}EZBic-A2T1rw<#rh z1)JWU|FmJ}L{F<;igckuLV1|~h7TYt3{1(Yv-^WCyx-T})=ceV&l{BL*j z{qzYQDnh0WOE@>BH41Mj{J&DQH$HQ>P^Zh&iCZ=;Y46l&x9ng@kH5WK%;G1*jk??4 z%x=~>cPc#K@LZm{@ryxC(~bKK+jKYXbrpG= z=l$fpx=^P}p2scMOVLM8IR|e(FkLk_BYnGlqIOO#&Y5b;EfYAg z+e1Z2*6Gr#Npj3>jI|B69PIzk9GZ1F=!V?9RI@XDf7oxxR`_36;3%F{$H)DXubB0T8R(r1rEW5h11R%&ybkR&X7}|RqgT7t>n^Qz2AQ~ zzKTs%n&>el#c0vBjO>Km1OWrPg1Jlnx&Dsh&GG-j`jY?6{JsCnJd3AW?O5`%!MHPO zZIrmIhsrLNN!O-)QBizrp&S@&vHI=i-2a#6CEt`wtX6k&`xBI)p0Hy{vC)5xi;k}} z-S=O6>E@Bir}T=GL5-n~{T!oT-mQz@xF>yM^J9ve@Wno1*PH$NN}s+6ce*Un+Yr0v zeogz5=*H&_^#Xkila?uReQvvX&-nItInVdZ<_xbH*H5)Ly7Rzfm*XExPt~(_x-44j z5j9<7Q>;V<{|&|+cfRG%wcYlXzu~S5uUc1mIZN3Dla>{ieYC6Is#yL?J@kz~a@M&| zE+6l(9Qb`8xb@O(*5C7WUmnZ(KeJQy&GOv+aeMMPzn$xw@1Y{J^;Go%|DJm~AFk(Q z7jJg(oXxmeK=!D=*8Z8DE{jZ!R=(%$Xn4kS!1@5g1Fzfrr~j^*$#61mQlBzcSvljj zsU@rhYu=Whu07|bYWXYm$hYzXZ{ObdU+=Ixv}F?;cc+U|;AD?i?jA3CnPV<~+4VMG zPmJpbmuj#*+eJVL}h?{YLwXmk<2VBWa4Av@=P z;LFeHb(i){>Qk5b7u&d2HI`K-`S)9|+X3%Y+(ETR*8E7-wG*}rs{UAJ@Im1vtJ3jp z>>C$Z=6AAxe0F5gpUso(R9!gcu|3mLTw5;oLGR(4=`NRiZdzu~eA)4<$z_q=37J1< z8xD8c*{v7cTiv%fjQ5}Cx-1Ib z<5iQXBGmb9qliK1J+~4*ecDQ-JC!s%Vx6fTRYgn`K zn&KosrhlAoSabAVRw*6&@3oHYnnLO07t>D_`(F{i$!zoVs;bc2=S8=dKhJf^PU-#4 z;CJFud7Qf1_h@F1SDs6>?GmcjF8l5o)n4%HKI^$@CY*{%1#{j89o^abd=K}I)pxE3 z`Kf$Wo28;AWZJo;u=CJQ=T6a0&pSK6>27`SKs@=kcEW-s!5zDf);9R6++~rO8#HIO z=cieql3&Am+kKXI%-2GTi~jC%N%_5K(cSk7U%s24shdC1qug@SipU4c4!GQWUmo^H zNvM;}{ZhrGGKPC^v;|4CMYsOHpNo23j= zx!=EJ{-UC_F*-@&qwD+(=1F0yQ#95wX4&cMP4rOVnlUlp*390389x~+)=fFK_3dia zyWT54ePP_kG;jM~C+Ty=BDx;AYC@eCCtjKBQEA-}ktofUllOc7PN^`}oc}Cy^j_K= zI&Q^L5pn5o%i;eH>)5vSu5J8Z`sHladV9@Gh70Gv9bEf%c808Hwg9V&H-o)!YnDFq zafa*a?fY3SJv$(Dn~vc6MyepNY~$o|0civ`)S+aYZFThJ`~%o zc&FlZdC9Ki>ed=v&k|9UuL@h9eOC@U`%X>fwtA7NPWq->EpI!g9-G;CyyJIK{<^l% z2_ZeRg(qE8llkM?x##lS#?zdkU5y6<9+Y~zPY!PGaw(m-Md9Pjh8|wdSID+f9xU{RL0NmAVr9R82P3>)*;} zKJ;?7d_{N1hxpjuX_rm?n_a)C>U!0vU1PLk_xs5@&6;PPh2^e}bIxl%$@un~=xVZ0 za=Z0z?snO&eM>wU(^m;~TB%&tn1AZq^2*zjGJH0@?^Lt>d9q`lNOO+;{MCu3ex3gq z&kI@o*&gkLcwmkCL-`oqH}Ox5*Lj)nMbCUDb$feb zFWS)>Io{(0RHu97=%tGa&)qY>+@Cr_>$ZmakAt6L=G zF)QYh!h_mH3jY`r9B;o?SKnW{Y5!U8#yS_h8vh4kp86iQ7A#=**cma8OKyYaW+_U9XR zX4yq$JR7VOJ2r_Uzkrrs0pJ7;e|!+9x$b{Dw|-rN+it`&`py>uxX3`G2V`R{TVr*m8lp zGydM$^s<)@3VaB#S*@O>ov)(LTSlRYon-_0Y}V<^?O32sd&|eN2qZ_lTM+F?(r);DNpa zsZWcJc>Da)W8#}$BBS{9`od0^wHL&uHa6An78ELqU()pTmqK)voy?6X8C$mM7Hn8D zxv_Y2n|be{iUZ8LI2J+#++DZa!JChknh6cDD}xE z%!Y#ZI!`>^$7FHs+lQ>17p9)9*&*F1t-99R=Cb~B%N=E|*_k(FpL~7u-0FZW>-zQc z|IS%B_4|DPnz+7WWxLH589ey$ZQpu9^Q5r7!Vix~R>89ngt!vLNGk>;l&s&Yh66bucaQ(e_NTSYjO`iXx?R!(@PAks%7rNuP zu^{7~`JK1cd~Uo~8DD?vSjDmZ?kpdVy*^OLcdpN{zVrX%VRe=~DO(Kj6#);|s= zk~wd8u-{gD`l6h{Q`qAcZ^iTYL5Dl)QuEgNEU;_uf`U-l`s3nwG>xieG=| zqJF)*F*5i48=;*yI$tIK_PgyZz`A}ugE+(cX`9n7O|;s=_mSt$ERHAZylwd11sR_i zPS~RU@owd!w~uG@UArfFf?-#N%lr%0cZGj+%+R&vS~G2XZ1Tpf2Ae*y_KHNVT-af! zpyd0y^-bJ+x(ujTOUSDLm@J^S!)F_md&{XV~>{Q z=hocro}VEvI(Pb3fYRL1Aux@A(P&CH*E zzfeA{?omr_#r+87U#`DPvw!VYd}6^nCm?CR?56!&jk)6e1^=2UtlYh%UdVWbF(-Sn z=rg~*(D`4rEATUb|0%!;RmWpT()|F3+?;gu8v`z+mmabx7&UL%LkhR zEd!@ZjFZ&PUEeinOJr(8z{XeJPgX5s`XaStf&UTXBTGIPD1A+s@HRiHdjDJToP3WO zy*5GNmhY;vx|v;Gx8`xE=xV(*d-OZma*@Ku?Qa*~vS`y!OSZ5t&=)L_(tK&*860}) zn^j6$0-sWcVOPM_pH}|OLb=bJllJ&r={n2i6Sc0{dP>8-Q+0P|7d+F~z2knV?8vD@ ztlPfp{n`KI#rB3JovN}97C)8?UA!flqo2Q9PedfgKE2vjN$InIV5g*JWNww47 zPb+aXGwYO@xTVB#eurzviqQPq&37A*#czw>wwF_}yj)$eYQmhOTtQdPF%am2;YGlqTx)rznM%{sLn_NnM@MhT?Tz)$7ivOj0j{60|N&^_w6}Oeywg4 zp4IpNdZpC{7sCcN|GSD-i)DR8S}b>0o_>-WK671e+$`BEfd@9aWPiJpoBes3^DDlG zE&rG+jDP#su&-fX;dK4R%PVum)-5ft{OhfB$trW}@rj1P<-gLue|xj&I(zzh`^XQs za$SzU2;ZE&qvOO>lY-2x>s52>m&V=o->h)p-+?2d7XJ2&+K;@ynf^c};ntq@`(0lh=79d6b2W5qL}%!|f1Uq0B5bupx#cWYGpSV;d+(VqlK3mN zNa8R1gDKnU>NdaE?@yHu-146J=Ih0qo^Ng3tfBQs_s5(m_pAyUd0)Q}?vig$?`!US z5$HN+?H=<*zoRE-9s2GvrF_<&w^^6NT;1n;oRoA}@m!*!OJmQvFQ#E8bAg^XcNZC2Z}#3NO0+ z*(KECea?Z@7L(`w2Qm+Qsl4AmY1_3Oi}}|CJO~L{J5OEuu;NXLe=aUA*;e{{qx$L& z+G?%Yn#Fa-#BFcv+}xgZ65nJCm<&p-`!Cx5uGZeF8@J_sNXwrOjV}GmB99fF`5A5T zj7xUs1xAw@i~U!W{n0Ki^Ks!8KYM$fR&4cO*RWL&UIf6&eUibYE zqPW#-?+bmE5!Iiuc*~w=S6sGyYk#w^pT+FF%n51kZ@blg|Bvmyw?FRNuLG-kL&fhl zTHsUQiu1Mh%fr* zqtW(P_x*3(sVC^WU!R__9+o_q&=>?`7elZN4>g{?_b# zA)udXAN;PYb;6cqOVm5n^o#}nN~LYAo%VMA>fHXf54UYsUvxN}!CE+|{JF=howvSS zVV?BMyGmvU{|nwXeow^a&%7+LFTI*Qp)6@?8haAY`SV3HciYJAz4l5_$->e|ba^v$eWznUbE{xy2woNZBQOv8z*&k`}a(Cm2 zjdF@Fmn>^qpmi%~h0zA_sx1 zNpG5o_v!cVq$)}pdw*w!8CjW$E}!16baX>%heLt>ZTDO2b_q>dQmy*+)d$8;aX~Ad zx!KI|Q!2XjUt!w+=O=Hki@MXHasH=^`@UPRPK6)4BAcM(wC+`Ml4ps;h8*6Qu2tJac~89FSe^zFWh-hUbSTOJ6MbA~%bB0slfat_7Vrlkdsa)gNqJrm(6p`cDJH z-j2N3)0dr0?IxP<6jQX_VK6mFJ@dBjta&GF|4%&Ox?gC9@%w|T+j)OTPt^Ze{LQu6 zFjSDJ-)hq=`oqVP1Ppi1){P6O)pez-(_q2Wu2qN&mzj^7*yZ*ANhgdALoq~8T&0eR?O<& z_I%ab7~?9F2jxoN^S|w3Un{qL)~@Mu3%)eU9dJDG{O0pdd|g_1vy)aUy6=B*Pt@;M z9osp^w0B27x=pLd^>PcB%rIo~_PG(JsFpkXk{@f!mfk}9-GXON#GU?Uckjfp#5sF% zwGVIg<(@Og(y>0p?_x}-Wx1&A-y9xu<*M2l6IlMaH}^a=>Dj4eUmlvfBG7&R1G%$~ zK56yKcOHJnQ+YeITY5v#Gnd%`vu{n8dLh16USyuocd~gU&(=s z>Pv$8S@)f=JHDNv`$?^R^p0b*iobBbOMSTHefT2j`1fUd>esz_e&W#m%hO)Z5c}R) zF*U&Y)wJ!oqC2jw}>m zx~nVR2B*zGs+Rt-&8yZtRYYlxdxvUkbPVpF~eCOelnwv@cu-*M;t&)ocs-$!G5vjuYgpZ~V+Ncw{krPv3$ zp3^;N?QPv2=e@+=mwS!>nsq9xJg&9XvMYW67o@anuj0cOaqr)}ymaEmzf0d19(n#< zV$sLz+>`PqZt+-mY-?i<&vxao#5Mu#9BJW+Zx`y?bW5|{j=yO)=Y*VuiwQ@S-=*A1 zVu#+%5rwSo9VsBu7IHXHEUP%O}aMii^)ZnQY$CJHECCt zY57J!HQleA=CIgvO6s}V_yndT*99s!>!bxYzngw+TYU9Ilif>BcX%AT-#PP@&bIQc ze;s$8n=E0yZU2T^j*lB`rbP%c9m0m5yJu9a%X9E>xqrd+_y_6R|CNq?;8-;K&E-k&rfhMU zx9uv|w)@-nYAa4DVw||@`?MwAs%K}dFMaBM@Ykey%Ci>uIeNT1m-n0L_WoztiC;4& zFV6q1o@7~Ya}k@>X`$2K1x~RgC)Yc5e^Iz?Uv}hzlge4mmtIQjg~CeTbHr{}-}<*= z^Kt(z^Mxi(ecG6=dRH%Vqu2cSOIIfODO_^oQ(APS`1ZX+-(F9(t54eE8L$7KA|dxd zsONRBS<5rNtIho#zB)5G-1QR6q_X8Z{f{g^5VcKwYi;ztYggH3Zmtmd_*2&7#8x}a zmvWxo&$I8wC-0C}EUdCyXtK5MZENoRJEF3Gl5*;^e`g$eXfF~WQlWHNFE-ZzFuUVfH#5i7W!^der!B9oyfCjVvO3-V z{%I?|INv4CCbk>A_3AxtwP?)RIcJ7znS|$Ok6mpAeAm=3vHrSo@AxYqM-YF*=!PL;R58O#6Aj(8b$_nXkR_h+u}x^?86#_i(*^Y1%1v^Kb_-qp|inRR=z z>F3+sGnUNtUDLKUv)b_X_U(V(t?PXIK1bfY=V4<@#ff@VTZPP4o7~l+IrT}^@=C#v zOO#$eD)kKaoTVNA`d0svyh&!tDvj&ajeNH${b@X%^MBszM$1LZ6dc4>{+F>h{Vh(l zeA+YBMN2<3%oo_Xjay)H!#lmKxw@ITi++4eOW?j;UvlEaiSM#LH3IdjPZs%Y`&%z- z8Mmm);}z!{$C^AfRkil^8+F@++3)`Gj;LG}@b6})hTCo#i)-Ji=We&Ex0~%(ZnMqg zc}~gU_^rBMPuI_A5xzN3|7NHC%qg8a+AS^mt9LZLpV|D*q)O#e_~-EH_g5*bwBDe- zah8?r2M&W1-~L|RvRH6}iDBTD?e>aJ9}Eih41GZPpdy#faQRVCo`5aqcmrh?1aoOt^?Hi&}n`h5>zx1c& zWVatZ`BQWEf7bh-9A%dr`fq_f_b2wn6;s~v7W5{kJ3WwIA$`WK0ZI`^W_eeO+7+L&6cCf0fJWKf&`)Dpc7%7PVA zYA*|uc;279dO%8ndybsSOP8e58vX=fPjg21>$5sspFPO;yz=O*-{zzny_&siIm}Og z-=eWTGD~?%7|)xaFTYl`Fdc|J@FUkYPW!o4i^uhw?Ge5Pg0gYzWRCksSDMfIt@DSo z##dygF7upACm(-P__tyI89V0h0T+%h3^Y4)cE`4-rq%{!-U1h=EmNzqPq%;1H0{jo zeO$fC`?E~El+-S@)+)VYop$o~FX=*Mwft&9Mh)Rp#o60FS!RUB8A~5_&Up8!#(qQ1 zz3aC-q$gh6X#F9vZ2f$ttgxv1{JAHy%O1)3^2xZeX#G<$G8S9Mxcivul;(R&cS^pl zGTESFkeV@Lt6JcS4e4c<-<__LcX0fj;?$Y2|4d}G((mv62R65!v)PdF?br1qK{p!~ zt#!T|(pcyFEhT-#5D-_cF2nzxu{HB`Hbs@8+iW^HM)=+kWP8M4=4bt@U(}|dd;3(bvKZy!2ebdZXLp*HduS1J z-o-;7dEQ)cbpFq$6Zhp7i;LW=-#0h@=Uy}G`+}0Ayb(ITnF^lO$n$*Bxs@)KaNTJB z_8XBk91(`^e_wq0I*Y-5eunUAWFZb11`NnKR6>&k!AvtQg&!Z02m#;nBs97c!ul8jAZc!_cOLJ!} zxq9u`jJb>Am#sKm5DscW1@bTQds?iuLuj@w|F;E|N7uZY&pvmD_}ro|x0Tm-zMY@D zKk0XB`MMq3l>fMByqmJ=wfSvvp}F_BDZk=%}jW78w*+FXH|a z@IdrhQfyt~pGAf>6HZjd9sg!rd2?>Yv2X9UwF|5~6fJ!5Np@qnO0MF{Si4~5q~F!I z)rIb#|1GH1(?7*~*A@?8B7n~%Gd-u}Cg)_h^H(6KFt=9rj2*Yf=4 zUZrWkzJYgQblL1B&o}X9u4)%yX4Ful8;8FE$Cw?mXGy>3H?c(-)Y&3{L~-8``7$fUPCM{+yl>PD#r>W3_YXmeJ^DR->$#0 zk7-7?hqN634z7y1;V);eE;Mx#i?N={apsNlk63=MDwj)IZ*Au-c{k~svXQZ$de+Wq zTlY?S+kPseYWia3&6m~oUdk#q&waCK=HH2Net#zBdAfwT5?J)(cK?#8s{(!NMZ)kPW6{~Ian`+t4YHzIVn0n*5(af)66R$X5YIHGp zoU+I+N%QvVn{`{7mX%AsdYQP1z|WR4o&? z?J0Ox#+0_TcHP@acPB(Ikl&aRYigAKktxZtwmNC*^R!D#)*tq`6?Ex;hw59&jyoqK zbM%vc&n=q2UO_d9&nYZdXx9meTmLS93pjN1#)Jtfdxa{TFI_tsxyDxItinqkC8M3< z22*k)W~Ns3NF0{$=(~{bc`Gbv_ph5OTKis8%V+5%S{so#~$8R{|> z|L@j4F7VOSdV+^yW%G*#Wxdi%nxl43*rKL*d%Hzogr&reLvu5fbu*cqvW&O0dERv1 zy7J_)oFiHnFCO^1^^2syE=h}fKTht+&2+l7?Z8KumQ9|r=?!oBSLYnJI8yX0vE_68 z=D7l`zZjqIl$8yPwq3S`@8kBJ9I3x<`;^Fe8VhD6TMIaD*}tn)y!_bNV+9HuG#>iM zvGZ*F8-116|4-`@$&N3pHkQTYUpcqqsoSLr7p||0E?fRvCI|>h+?p(Keg@Bu){GNZ zRWILAIqS8iWy=vMmt9}J>%RG1B6dUWvrOre7w?((MD->0oOu{Oy&^)-#e;#5ZQYrL zCw45GaHS+(Ozqui!vmpDcj!-grnaku@6DTyrxH$E9C+p;6=k25b1B@rZ{HHx#^lb_ zH_PU+GUwD!+8lc~!%uZi_cOyi+rs|!X+M1NzceS4_fdW+=cLUuq$gDg^8G8j={;TO z?ymd)v*oYMW^&*EU~g;k4@Seb={NK=Z6>bZah+@un8fq{*7l~peHY@6Xk|YBz<#%4 z%FfD}sy`>56g<}Pva)+=WlyBkHsfEDkC|`ZHhIa)j(;I@pEhswo%ZVF#rfhtPJKI` zYWFVci1^#2l8Y}Q%sxiW-@(rvptwBz(i4}ih*-?;WIz4B&AR}PQ4n#{k-l?ywoRb~I~Kc~3X?ay01-y*TqZAX%oggaJGe1S^;m)AqG>NH9xUQp;;wef@uJGvBNFp2T+&Ky zmHjK;>3MAXQ@gTtHs^fqoU8iN-^yrcIDdo8_05L*mwLZhF8q3M&w(Syc0bAGxL_yy zHz&xT;fK-cZGA7;T7n#x_T8U9q02<^^%iA=xUK6qpZXrsGWoYr@RQ;#jopKUg*xgFz;UTqPDh7?bEgMM0c?K=$*iACp%-|)bj@;tRIWXR=X;xo^!G9 z+}qgsY2P!q+Z|08xj9e1o8Mp@C4IPQB@bM<%^YW?7RIf=)fu$m#0h*Vm)_zNbL`L zewNwuu1c=o6El9*cdy+J{MpvNt7xTmz0l)`Vv8N~ek9#$$nYrP{IG3_?53K-ZElww zC+%bPQ{AO-kHsgqI`($_lkC{nDajSe6Z^K?eN5OXQQ>~6uh*_^oBW2oOo~w-#1=Z* z#$38LN$u3t9c&Jl-b~VCzNZp&>>LBA^OWk9ycsm<*Z7np{YA+GThHHKuj2ZOOHx(N zYP}Rny88b8k^3IgBRJ zo~i50w%eZhR^*cM)b>Hlqr{!M?d4YTYB&Zu20H= ziSe6L&OJK2O`PqT;;q_vk7mDL3@Wl}nIaSQ3=9io1rOf8Pd_e7KBOSCzUS35f~ZP@BiHCoJNulbwn%^(%D<8j@ zv7}Hn*V$&pI)zXFuWj1f@iuyO#;Gkwx;$eOewW?u78HFif7#~c?Y)9?Ts_@Af6ZIr zIiq97AK$#{%ZK!zRvr5$yG?v~CC_=qV+H4{=I_|v_V#$rdOwjWJ9mo}%Br#}YG%n@ z6P@&IVhOKdWYp{#9N)LqZdkoUChd3O?Z2E~H>WoUcN*Hb1f9>i5pZ6_=Te+{*iS!| zV;>Z5&wrjB`&c0})APH)Ss`)dDP>hR<#MX8+_)E>r21F=fqhHO2Q$yAZ8ldXFSeiZ zW6^4@$duI31Wl#(y?Kk0Cmox-#dc$lJpz?D<~ss9&k8W7PGmBV9-K zcRu}6HRns|uB?2|m@bsO!+&pYV)e$qQ{Han z&^)frarV{3NqoNN9?kHW<$vkxk?((B9dX&Q9W-S;v)y;w{B3*96j%O|2+Xkyy7X|8 z+R4MqqSR(DiS_ombWQ19c>eA7YtB+hdlfP*Jr6gybEf}gcw{G^zgt!F#6^t=&-ji2 zmp{`!MBbaSC3f-h*sxhuMPI%vbe%0a_)U1*^#zujf;L|?^nC1ntMi2Oo{klk5l6pW z+V=kB#VtEIo^R6ISUdl1YUlZfw@sd(zjk=Z`q@h+bI9Iv?wIy&x==0ewlei!!B>P? zS5|5Z3O~5q(fRvd?>wQz-;uXpGja<>T|3V3-djo|^HR;))AF9&Ub{Lq1Y!g&CKW{W zZTVZe?;7j2`J4CJ8Mg5Y9qoPZ{cHM?lb^Qg)vKJ1H}H&}da?NMYdzkLVH3AZTGHOo z-ue93z9Y4EJ2#&fy1o5ooJhoE4!<~`E2rLG=~%h>Drimp=T836btx04mU7JfdA#%K zomm}yp0gY-<+a>8@zdo=5$o;KxA)om-!{6ZvgH4(S&N!>USILn^Zt%We(Jjl-}#rc z?_+q8S#5pW{%KZ%pGfaR?E{`iB;;bUlOytS_GeT(FUovia7a>W=F%eHdFxBBs%y*a z5v`baLffvVAgXU$>|~?%ZSkA-8XCweR$EUf_|YF`GW|`EN0i5@v+7l^FvkXf8ONo~p&^^5Eea+JjDC2worK37{5a^TDXu}bMcy)Le2^Om;-DQ()a zc5^4kzk4lTo>)5Y-?p}XS!(O#}V{@jwgnpi736 zmYuo2>y&aPr)Rm4_Lgp8*}&@CcI)DTZshFGsxD6A$yX11?UwekGWcnB?9+)`W-Phg zx&Q9cj@R$+M&(s3s`ZF+z9f8PNl{ut`6Bgmk=yFmJ=vLiKf78oA$5`AgPTXZe#Q01 zrHAUhI(f0aF@r5QSj08#9qT{Wsf&^)sZILgKdZPeo@v^Ky}RBX*Q$KS_epi4@|^$? z(KEK5Pnw^ZzMSoF>1Rvx{^)O?BwL#I?+(6xWs*qmLtzvDJN-Fdrf}R|fBT>HMwWW* ziw+q}mONJVox8sD>GA~yA2^i)Hp?$OP!zOx*@l~ShrdlbB5}{fC(6ysD)5rYS^ z6=T$69RavY2Qu2Xcf>lC#LfGx+A=VL3Ch*72i`ZB>xnf;! zz@&M(Q>%);ELT|aKWoyW6Q|}L`IPSp^2W3+_JI;rS|4;ata!UM$3FC2@zj}|4Q<6T zvoy43{MMgnAAQ#IzUqPU1AZ+rH#Hr~+s~CxfBEH(WY@6|g0s}$@y-`Y{H=TYKf^D< zTi1RI@?V;);yZhN>D=Wj3JUU)magu98=U)p#`@By2fy9kww>`A!#dSj_j@OlbS-)L zY3sVr9#L+WYF%!<`E%s!*NwGad;KnD@v=&LRLz_5!=LdD!)9SN z^2UFeE_%jP)Yi`7Nq1P$rE<%Q53-{dDc=0p(!77a_Vqt&7HUcz{+&5*hk1=x%iHqY zd~b{HaACfW9G=TPXW4;A_e8n5*wdF=zI|}UYq{)`R?u80dz$hSuI~z$Wc{X_IPbf* zRo+wEqpE>p!58LlDn*C=->)ild#YCa!CbBDVm?b119O9APJB%E>VtegA6#uv?Yy#9 zxa0jw729YRTgzYT1zs7JItHesYRm;?d6!Fx2Y$OmooIFuzs;MwKOpb6_KEN#ubihV zXjk3o=&-r#Tl_D_QmxijHP-jUgul!`SS@aSJF<;`qxQW&YrF%Jc=#7J1}kxG{5$Wh zENA-1A|=r`ww~!;vkc>3N4Z~GHR+$X&$jA`$+sNiZpdX;r!Ok_6qF#I#PeS_b6L&W zY*|lj@2ZxHXJ?t-c==rVcX!3)Em?~LQf|-ruyGypG4*+x#kcmYxfb4>YoA%|>$LyB z=Yxnzvu*?}=U9i>Qvz3(8-k`+G+K$emkGD-- z^Pr|cbzC-3uAjb*R#-6HUx)8bGW|Ls!k*r?YcBJ0k1>!h3e-to_EnbI+B z!{2>xV>#SkCn=fc>{wE)a(2%8(qyHT`0nB@ORaOj}C#|q6e zPct&URF&%LYv&99EQy_@vv&2$7a!ebeNMk^^m2C4!jFGNHyA2Tuk3g`Z(IMmEpIy) ztSI>E<`kFLvc&k6;;FlmIrS;k{0sC8eq7@O&C}#q4eK$d=T5nd4c1`ueU@Nxy|}{}$l;zrS&9N1K_r_pGI}wxv`a zeeGG*$I~u_WbG6Gf@iNPvktF^!vG-S*Oq3x7Ak3|Msa~r|C7ztm3C?SVyY7 z6;N{erCz`}-&j1_n6Z4bgrVYpsUOqd8h_qwVrZuFm6cCbMnstZ(qjW*rstCDC(JLM zs#=}#&flj@jb$AR+wHpLP0vL(Fbb}XW~i7t!Da6{G37~4dm9zE{<1v5b>_Qj6W`rT zxx?GFEBpixb}M%n)-+2Tp4F$t&C9sXaNlmG<8^{B^8aMs?328kvR!qa;ogozR~@(I z>t45f#pqcqwAaZ@IqTLrX1`6R%h&(65`V?NCV!9j&i=cCUY5U#1&>P1eY&*hoPcUB z@1Eq{4@+(^Zi;!kwu3$N>{o6L_M0iWXP>N@rgnO&Ez6CGvnAUfH>yjr%B1|>Q?u54 z3#;eej;5cwjrSvF-L9CBX)|y7*_&IsChK;;_tsccq8?_|xAAY9`A_;{al(YR*4EW;s@yKUY3aH(?ZA4Mv!^<@*=ydm%sO*XUb$5Lv}5f?p6H$X zOYW;n>6xl*voFqIG+#1#{zuz@jT-TLCoe61%cbMTzt8QDn}tvJ(~WcHWys&`x;o9_ z|C^=N5%X3{_V3cldabc-|CU+_K~tM)FVg8BK;1x_hk7t)^pnwk67 zzJ=4~{;4|jEj%)?KHrHU$rm&~HYJnCTwUh(!UOUL+z!-iYrob!Ii4T1ge1;I`RwLK z{>FdXroVXQXVm)`|TwzS0`jn|2k#ye2)^&9Q)&^Yz`Ubcsy~)=i8(Da+8kK z#1)E0s!NnRN{%IStbN~_yZ&%O+}W!;WH$UQeWvqGX^MUrd)&m5xl4ZDDqh2sI^jC! z)lKgkx8~~S{GRsYK{(s(`#0oVEdIDNtiJHmFej+|XXlJ1x+%Mtg(tn@^t`M3mb*t` z*D*f!u#J1SysiGUb=|iD4W&HyFV}X=2`H-yzf^hiRL%S=Bor%(~uWd`TH`g3?>GPOn6MVdC{)+|G_8S`B@`9Gc7n(M_&F7wT^l+nS zqqyo_jm(47?#_$>i#TsD&f!-(_CX+_GKnWY>#>B`s@*+G zYMI?jJfnUFnE2W>6nyYkeD}R;+w{$~_4}?}wcF+nDqe*a&3h>8DW1ADywu;;@>jpG z@fDZ9pDY#2r)IE6MqOuYIb1KaL2u)ny0uFxKW$z2S}}8#kL>GOrR$&b7VST(wk`hL z^*RYo$PAW+x$;6qiusGSXpxy*iCHvIcJHc8XdFkt{ZN^@>Ca?xS6^0SY+|-dw0HN z|BMR%opSC`=aJ{-^@}DyHJ&d=61wMiwjO(v1~;O7vB|*ZS`AfxdcmYv}gF4 zY%}~bV~SN!*`|<#jV;afIu~>6G;goIweEJD^VX)t@h9fB)O@(<(%)#V605i}e&4my zo~oiR|9!U@`CrjrFU%U(={G^tR?ct9W?rT+wzvr;o0qhH+Pd!J1e3W-f;rgh;~%gs zvi%eGG-S$4ttka!;tI1A=lP4gUcMrwH~F{v?R0^27vx&PDlbd)DJ<=qGFK<`LwrlK zJx2vo#GP+ac>$(}Y&QP2ci%r@S)8zRkXm75mT}vjw6&9-F4#LqiTP{ylFCNi%_~Y4 zv9{!~^_kagx>~!`lhgBXJyWoNZ#vt}x?P*X%*-aGG4LsMZ8@Ki)XKoXe+P6fz%j*7 zJ?yvZ)}KA@@HeAC_V2k#CmHgbYxZydRk_~XsPgRc%qt03RvBw9p3$lQ$!z%{ z9lkeFx|L^rwtbs_q`byINwqLw74s@R*S}6`YD{+S)4i^*F|9mXRrKXIXL*IL zDi(WG->%clkItI=&Ei-6UiVj?9<6WLeO#aP@q1mI_idu*8d;x=fbKOvr@o!|)-J5* zi*Un*mIFeSwr2;%zH1h<`ZY6or~Jpcc7_GQA8Y5$n{OF(MSlr<0r$fX|4l!x zGIg)Nvxhf>{j-pF)5(kLhH%fz1}K9vAw!KrkyktX3}R^e!EUNf3c8m^6zLR zi;~)Jm$Z12W$RM@$efkxtt$E=&XRWSc8AOI8Ivx=`-KSJw!dxHJ;D5LZ->Jj^_PX7 ze?MTt1L}V>s88R z^IR^R`INb%E&A_{8Ixb0Tfb8(+`-oJ*Y?RpyI;@c?+|;@DYM~k_uJx5z6NW?%gp7T zRbH2(eny2?s+`q*DfPf~(aRg1E`>iDx7nY+zRN}F;?tMM=I=RS*|OwuUCWm@k0-sG zyd`JfG1XZUOoGF>&F8FGx`S!P-EZHvWv|_;Jo9^RLp z(H)XL)p}Cev=Y6C58ivYd`REAx7a=8<|%K!~RZ|O1mnst$us0rUCoH$~q7XuH?%6PNVPE6V(h?Wj8XTg6r_^J~r8>=@8W zB_XGYx!wpHJ;D0*tDeXA*(X#>Ize~2vRqMXG8_XFdCU)rEp$13wn2$?YWG8>>nn6c z1h%-evORez|Jru)WoJJjo9g%F`=8s)pJ{8E9)GV~)pGOsTl>rZUh8^S#k6DFTZN>X zIr~$7D^nDFeKcVp`$KIv~Q@t3|$dKN6~v3>EWBg=07tG<1Hn{=$pks~+al77dm zdh@CFfZ74`WvA>`J5DqSzQh{hzO6a()`o{i^vrT3Ud7)@{-d{{?zZERmNmX#%lZ|s zsr;GFn9q36d8_MO_B-k?IXqWylX@UAscce-ZUOhzxvvk0be>bs`M>Gz3nith)i={i z?Lx~cRW>kse~)~7JF7reEpywF%8;2K|M^U6jArv!)=+9mnJ$-7y9dZ;BqdzpztT}2M4z`cZ8q&c0D)W zcQ5C^o>yic8kT(bmU_`{?z!807w3+NyITG_KJeZ4|Khje<{nk0MY(6~R<94>mTM2< z&I)HJ_q5XB{J*L9)rm6m4eZ|1W^5CDAKtq!zjZd zv7_f!dg+S|?>9?LF_gVo#^|?5=ufbE^5HhUzsut}YPP>sNOF>6vb$R*U|6_qe`Ym% zLiww#+s_-?Rb}-v&&JL3z9jFxi+6@U?@z0?SFwLAZ~wo2y4cmFBsRFL($cgbyFP3F zRo4T1C;dyg@hy_~)Qr9yYXO3cvKdadiW4=N%suSS*m0jdtP?GyM5&n%hMIVb{=S&`aApRgkADKDsDTvyuTTjP;H*z zp2hTbd4oG>f5Y6Vwtk=IeUN)K>D7LQ*FlH(q}}|t=&kr~fjXBXOVWO8-M$_0bhAHm z{30oB8QT}NVa-aJ?wmQbiwpr)Q zZrQvtICb~{~)opjIUQ^S(8D}(x0H&jmY%aQvfI`vBbR`IL%t#`ft zdZ00vUGQ)6Z|iN}S+*^|Cw^{;(7*lDw!EuYKY2^n66Y2CoYRAyck{77yRq-Ww|q8; z1FQGFeY^7P@_y!c2DvFEIs3my@^&qms~XFH$EQ@qYM$4J^YglRrKin&Vcsk*x9M+0 z@pYyL{42cePJf!d?f$hp`z&7lySeTD&6=(~Q(oo!ZdQC^l-2(E&9ZCXCo^o5XRq7T zlbQcL(0LevaCB^*x%lM>t6e;v|8;CoCy<`?4P>j-igmDYqee$Jdk@Jl=b;W zb>rFG_$e&*N{hbd#AjE#Czxl=ziJ=cEBY@f=c!w`$1UGWOj#Ez*-Tw4+TIo~`&75? zW7rpdwsmqpkA3scwck4D@t(yu4Zi=k zs{O5~D_Zd-*R!r%STjQ@r~dr6eFwrq_W!=TqUFDcr+c9MgZ)mqV%7o`-EX~f_g{&+ zASf8z@>V+My;G@K4TD8{)|w9o{^Zt^) z`P1KX;%3J!8Fs{9+U;(y@sce|wVeCGlxOcG4{#s&d8O+<|1qE9>Iu`Qzs%uT<6d$z z>PpMw8A&ys@`o@ru7O&zC124to4{N1g14Bd25zd^@QeZ|?ceeZ{Wd6OWhY zn%$n~_V zGvE14GlYHa@0BYLh%diVeDj$17kRd2%>BKee^k_QoSC@g)C=$T+r&M8nS?fTf8hA= z`I%YKzR1OW#=XY<^?u(o(PJWqldTF^3LbIIGk+>6f2WM6cB_@C-? zamDBJPoB;;yBxRq_q*k0sctSVMNK)e)2Ke*{dx}UU^pOVr>30wTrGUE-vmj_7z{TTm5?F zx=#CPUpA&4+_rqywA1gGYPs&?`qQ@RO~vfDr#H{feww+bUfVOAN&WV|>IICR(G2pN zYah(`ZO%Gx>)$5|k2~*W+Wp;s@7vzo<3fUhhCVItSiTwkd%rC?a$?$sm!4M+y-CW_ z`n5dA-uU3-bzY^hHl0y2dt@!He!F_Q_Mw2_E$$iFe;T(XH$LS#vuW@B1(W|g+hZ>? z`-%BB)_WE&Di$q|^Imt##l@v$*4ye_dA2hPrB?po4ZT#nYsvLJmmYgY`Cgj8am&+B z`VD(?^KUOzsh7~rH+r|BlBojJRB)Qd{BQMJ1tle=q(?dbPka-+^6Ix}$jNU?p6MR9 zGF;c^3;nTQ{_R8U%u`>#ZP>f_?ax}C+EXur7X4QWT`YR{tB|R^qLPx58s}~Pn|57U z%Zd!IraTSwO!vH%^5(PZTD_MNs}4W~5B7Uc5g=szc!JR=Lud z`XJ4cxBG9^*>8|6P%NR!J~Qj_`?3;iixnWpo8!(YGK=G(-7KDj#GCH@lM zq^j8NT3Jb(%ccE{;oD?gTwGkf^uLwAZO1pm(bv&_+N_0JI$j;A(Em_&yFTUjvujLq znT|=%Fb%xTkbUX)zVGhL_tiU-rk`c$V{A)OuL32loBl8AZ}BZkU$yb^_MQFJGmh`B zoYq?@KL25?4UdoTtZR7OGKH$z}7X+CgT9WDdzaruKw&o7bnap!8l?i}^gB_=beX(~A`?C98+=DCI znn5Pa;M%oi(SG|4p`ezZ4|`vRHAu8$Y1i*mvwh>aXAulVX4$#uwfCNo?nzf`qdWB zQ~NdP+T<-ySG-U6^ztnO`Rqu;$+CQYjk~Nh+y)z#zrAT|>j)BdSrcGy_V^yBjGyW+ z?q3CeASN}|U3n4DmBX+{YR4}ABV7zTMB+p@#jk_ccpU+qBa* RfPsO5!PC{xWt~$(6981V1up;q literal 0 HcmV?d00001 diff --git a/docs/design/tilerendering/chunkpositioning.svg b/docs/design/tilerendering/chunkpositioning.svg new file mode 100644 index 0000000..9b608b9 --- /dev/null +++ b/docs/design/tilerendering/chunkpositioning.svg @@ -0,0 +1,366 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + 384 + 192 + + + + + + + + + + + (1536) + + + + 192 + 96 + + + + + + + + + + 384 + + + + + + + + 192 + + + + + + diff --git a/docs/design/tilerendering/chunksintile.png b/docs/design/tilerendering/chunksintile.png new file mode 100644 index 0000000000000000000000000000000000000000..9dc9cce06117bcbf3ae5fc29182a12eef259224e GIT binary patch literal 27189 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 JWt~$(697RiC?Nm< literal 0 HcmV?d00001 diff --git a/docs/design/tilerendering/chunksintile.svg b/docs/design/tilerendering/chunksintile.svg new file mode 100644 index 0000000..1fbd5d0 --- /dev/null +++ b/docs/design/tilerendering/chunksintile.svg @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + Cols: + 0 + 1 + 2 + Rows: + 0 + 1 + 2 + 3 + 4 + 384 + + + + + 384 + + + + + + + + diff --git a/docs/design/tilerendering/topofchunk.png b/docs/design/tilerendering/topofchunk.png new file mode 100644 index 0000000000000000000000000000000000000000..c67978c55e2994f624a9258d1bbf08fdbfd62a6b GIT binary patch literal 48432 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�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 + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + (Actual ChunkProportions) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (DiagramFrom above) + + + + From 64e67cc827d36f0a6af079ebc8185484d94e2f7e Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 30 Oct 2011 18:06:28 -0400 Subject: [PATCH 11/30] fixed crash when SpawnY wasn't in [0, 128) --- overviewer_core/world.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/overviewer_core/world.py b/overviewer_core/world.py index 745106a..decede4 100644 --- a/overviewer_core/world.py +++ b/overviewer_core/world.py @@ -266,7 +266,13 @@ class World(object): ## The chunk that holds the spawn location chunkX = spawnX/16 chunkY = spawnZ/16 - + + ## clamp spawnY to a sane value, in-chunk value + if spawnY < 0: + spawnY = 0 + if spawnY > 127: + spawnY = 127 + try: ## The filename of this chunk chunkFile = self.get_region_path(chunkX, chunkY) From 69c109fc05ff1cd2da02b817ec8244d1c9c591c0 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 31 Oct 2011 13:28:28 -0400 Subject: [PATCH 12/30] moved trasparent_blocks, etc. into textures.py (Issue #516) --- overviewer_core/chunk.py | 19 ------------------- overviewer_core/src/iterate.c | 2 +- overviewer_core/src/rendermode-overlay.c | 4 ++-- overviewer_core/src/rendermode-spawn.c | 2 +- overviewer_core/textures.py | 19 +++++++++++++++++++ 5 files changed, 23 insertions(+), 23 deletions(-) diff --git a/overviewer_core/chunk.py b/overviewer_core/chunk.py index b8af17a..e638350 100644 --- a/overviewer_core/chunk.py +++ b/overviewer_core/chunk.py @@ -125,25 +125,6 @@ def get_tileentity_data(level): data = level['TileEntities'] return data -# This set holds blocks ids that can be seen through, for occlusion calculations -transparent_blocks = set([ 0, 6, 8, 9, 18, 20, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 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, 96, 101, 102, 104, 105, - 106, 107, 108, 109]) - -# 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, 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]) - -# This set holds block ids that are not candidates for spawning mobs on -# (glass, slabs, stairs, fluids, ice, pistons, webs,TNT, wheat, cactus, iron bars, glass planes, fences, fence gate, cake, bed, repeaters, trapdoor) -nospawn_blocks = set([20,26, 29, 30, 33, 34, 44, 46, 53, 59, 67, 79, 81, 85, 92, 93, 94, 96, 107, 109, 101, 102]).union(fluid_blocks) - class ChunkCorrupt(Exception): pass diff --git a/overviewer_core/src/iterate.c b/overviewer_core/src/iterate.c index 44f30f8..618c3aa 100644 --- a/overviewer_core/src/iterate.c +++ b/overviewer_core/src/iterate.c @@ -53,7 +53,7 @@ PyObject *init_chunk_render(PyObject *self, PyObject *args) { specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); if (!specialblockmap) return NULL; - transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks"); + transparent_blocks = PyObject_GetAttrString(textures, "transparent_blocks"); if (!transparent_blocks) return NULL; diff --git a/overviewer_core/src/rendermode-overlay.c b/overviewer_core/src/rendermode-overlay.c index 17b760b..72e58ff 100644 --- a/overviewer_core/src/rendermode-overlay.c +++ b/overviewer_core/src/rendermode-overlay.c @@ -38,8 +38,8 @@ rendermode_overlay_start(void *data, RenderState *state, PyObject *options) { 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->solid_blocks = PyObject_GetAttrString(state->textures, "solid_blocks"); + self->fluid_blocks = PyObject_GetAttrString(state->textures, "fluid_blocks"); self->get_color = get_color; diff --git a/overviewer_core/src/rendermode-spawn.c b/overviewer_core/src/rendermode-spawn.c index a5c0a27..c303f23 100644 --- a/overviewer_core/src/rendermode-spawn.c +++ b/overviewer_core/src/rendermode-spawn.c @@ -72,7 +72,7 @@ rendermode_spawn_start(void *data, RenderState *state, PyObject *options) { /* now do custom initializations */ self = (RenderModeSpawn *)data; - self->nospawn_blocks = PyObject_GetAttrString(state->chunk, "nospawn_blocks"); + self->nospawn_blocks = PyObject_GetAttrString(state->textures, "nospawn_blocks"); self->blocklight = PyObject_GetAttrString(state->self, "blocklight"); self->skylight = PyObject_GetAttrString(state->self, "skylight"); diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index c7e58d8..f4416db 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -2321,6 +2321,25 @@ def loadLightColor(): lightcolor = None return lightcolor +# This set holds blocks ids that can be seen through, for occlusion calculations +transparent_blocks = set([ 0, 6, 8, 9, 18, 20, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 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, 96, 101, 102, 104, 105, + 106, 107, 108, 109]) + +# 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, 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]) + +# This set holds block ids that are not candidates for spawning mobs on +# (glass, slabs, stairs, fluids, ice, pistons, webs,TNT, wheat, cactus, iron bars, glass planes, fences, fence gate, cake, bed, repeaters, trapdoor) +nospawn_blocks = set([20,26, 29, 30, 33, 34, 44, 46, 53, 59, 67, 79, 81, 85, 92, 93, 94, 96, 107, 109, 101, 102]).union(fluid_blocks) + # 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: From 75858f2df80fae3022fd48de73f7e23f7f2e6016 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 31 Oct 2011 13:40:38 -0400 Subject: [PATCH 13/30] removed historical, seperate mask from texture tuples (Issue #516) --- overviewer_core/src/iterate.c | 4 ++-- overviewer_core/textures.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/overviewer_core/src/iterate.c b/overviewer_core/src/iterate.c index 618c3aa..d0ab6a2 100644 --- a/overviewer_core/src/iterate.c +++ b/overviewer_core/src/iterate.c @@ -450,8 +450,8 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *src, *mask, *mask_light; int randx = 0, randy = 0; src = PyTuple_GetItem(t, 0); - mask = PyTuple_GetItem(t, 1); - mask_light = PyTuple_GetItem(t, 2); + mask = PyTuple_GetItem(t, 0); + mask_light = PyTuple_GetItem(t, 1); if (mask == Py_None) mask = src; diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index f4416db..73cb5da 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -517,7 +517,7 @@ def generate_opaque_mask(img): def generate_texture_tuple(img, blockid): """ This takes an image and returns the needed tuple for the blockmap list and specialblockmap dictionary.""" - return (img.convert("RGB"), img.split()[3], generate_opaque_mask(img)) + return (img, generate_opaque_mask(img)) def generate_special_texture(blockID, data): """Generates a special texture, such as a correctly facing minecraft track""" From 8e0a82ba62fc025fe74c107d6db759c17c473f0e Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 31 Oct 2011 13:58:25 -0400 Subject: [PATCH 14/30] unified blockmap and specialblockmap (Issue #516) --- overviewer_core/src/iterate.c | 65 +++++++++++++++-------------------- overviewer_core/textures.py | 52 +++++++++++++--------------- 2 files changed, 52 insertions(+), 65 deletions(-) diff --git a/overviewer_core/src/iterate.c b/overviewer_core/src/iterate.c index d0ab6a2..dac8587 100644 --- a/overviewer_core/src/iterate.c +++ b/overviewer_core/src/iterate.c @@ -21,7 +21,6 @@ static PyObject *textures = NULL; static PyObject *chunk_mod = NULL; static PyObject *blockmap = NULL; static PyObject *special_blocks = NULL; -static PyObject *specialblockmap = NULL; static PyObject *transparent_blocks = NULL; PyObject *init_chunk_render(PyObject *self, PyObject *args) { @@ -50,9 +49,6 @@ PyObject *init_chunk_render(PyObject *self, PyObject *args) { special_blocks = PyObject_GetAttrString(textures, "special_blocks"); if (!special_blocks) return NULL; - specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); - if (!specialblockmap) - return NULL; transparent_blocks = PyObject_GetAttrString(textures, "transparent_blocks"); if (!transparent_blocks) return NULL; @@ -380,6 +376,9 @@ chunk_render(PyObject *self, PyObject *args) { state.imgy = yoff - state.x*6 + state.y*6 + 128*12 + 15*6; for (state.z = 0; state.z < 128; state.z++) { + PyObject *tmp; + unsigned char ancilData; + state.imgy -= 12; /* get blockid */ @@ -403,47 +402,39 @@ chunk_render(PyObject *self, PyObject *args) { } blockid = PyInt_FromLong(state.block); - // check for occlusion + /* check for occlusion */ if (render_mode_occluded(rendermode, state.x, state.y, state.z)) { continue; } - // everything stored here will be a borrowed ref + /* everything stored here will be a borrowed ref */ - /* get the texture and mask from block type / ancil. data */ - if (!PySequence_Contains(special_blocks, blockid)) { - /* t = textures.blockmap[blockid] */ - t = PyList_GetItem(blockmap, state.block); + ancilData = getArrayByte3D(state.blockdata_expanded, state.x, state.y, state.z); + state.block_data = ancilData; + /* block that need pseudo ancildata: + * grass, water, glass, chest, restone wire, + * ice, fence, portal, iron bars, glass panes */ + if ((state.block == 2) || (state.block == 9) || + (state.block == 20) || (state.block == 54) || + (state.block == 55) || (state.block == 79) || + (state.block == 85) || (state.block == 90) || + (state.block == 101) || (state.block == 102)) { + ancilData = generate_pseudo_data(&state, ancilData); + state.block_pdata = ancilData; } else { - PyObject *tmp; - - unsigned char ancilData = getArrayByte3D(state.blockdata_expanded, state.x, state.y, state.z); - state.block_data = ancilData; - /* block that need pseudo ancildata: - * grass, water, glass, chest, restone wire, - * ice, fence, portal, iron bars, glass panes */ - if ((state.block == 2) || (state.block == 9) || - (state.block == 20) || (state.block == 54) || - (state.block == 55) || (state.block == 79) || - (state.block == 85) || (state.block == 90) || - (state.block == 101) || (state.block == 102)) { - ancilData = generate_pseudo_data(&state, ancilData); - state.block_pdata = ancilData; - } else { - state.block_pdata = 0; - } - - tmp = PyTuple_New(2); - - Py_INCREF(blockid); /* because SetItem steals */ - PyTuple_SetItem(tmp, 0, blockid); - PyTuple_SetItem(tmp, 1, PyInt_FromLong(ancilData)); - - /* this is a borrowed reference. no need to decref */ - t = PyDict_GetItem(specialblockmap, tmp); - Py_DECREF(tmp); + state.block_pdata = 0; } + tmp = PyTuple_New(2); + + Py_INCREF(blockid); /* because SetItem steals */ + PyTuple_SetItem(tmp, 0, blockid); + PyTuple_SetItem(tmp, 1, PyInt_FromLong(ancilData)); + + /* this is a borrowed reference. no need to decref */ + t = PyDict_GetItem(blockmap, tmp); + Py_DECREF(tmp); + /* if we found a proper texture, render it! */ if (t != NULL && t != Py_None) { diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 73cb5da..36af92a 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -496,15 +496,22 @@ def load_water(): Block 8, flowing water, is given a full 3 sided cube.""" watertexture = _load_image("water.png") - w1 = _build_block(watertexture, None) - blockmap[9] = generate_texture_tuple(w1,9) - w2 = _build_block(watertexture, watertexture) - blockmap[8] = generate_texture_tuple(w2,8) - lavatexture = _load_image("lava.png") + + w1 = _build_block(watertexture, None) + w1 = generate_texture_tuple(w1,9) + w2 = _build_block(watertexture, watertexture) + w2 = generate_texture_tuple(w2,8) + lavablock = _build_block(lavatexture, lavatexture) - blockmap[10] = generate_texture_tuple(lavablock,10) - blockmap[11] = blockmap[10] + lava = generate_texture_tuple(lavablock,10) + + for data in range(16): + blockmap[(9, data)] = w1 + blockmap[(8, data)] = w2 + + blockmap[(10, data)] = lava + blockmap[(11, data)] = lava def generate_opaque_mask(img): """ Takes the alpha channel of the image and generates a mask @@ -516,7 +523,7 @@ def generate_opaque_mask(img): def generate_texture_tuple(img, blockid): """ This takes an image and returns the needed tuple for the - blockmap list and specialblockmap dictionary.""" + blockmap dictionary.""" return (img, generate_opaque_mask(img)) def generate_special_texture(blockID, data): @@ -2425,7 +2432,6 @@ bgcolor = None terrain_images = None blockmap = None biome_grass_texture = None -specialblockmap = None def generate(path=None,texture_size=24,bgc = (26,26,26,0),north_direction='lower-left'): global _north @@ -2443,7 +2449,10 @@ def generate(path=None,texture_size=24,bgc = (26,26,26,0),north_direction='lower # generate the normal blocks global blockmap - blockmap = _build_blockimages() + blockmap = {} + for blockID, t in enumerate(_build_blockimages()): + blockmap[(blockID, 0)] = t + load_water() # generate biome grass mask @@ -2451,32 +2460,19 @@ def generate(path=None,texture_size=24,bgc = (26,26,26,0),north_direction='lower biome_grass_texture = _build_block(terrain_images[0], terrain_images[38], 2) # generate the special blocks - global specialblockmap, special_blocks - specialblockmap = {} + global special_blocks for blockID in special_blocks: for data in special_map[blockID]: - specialblockmap[(blockID, data)] = generate_special_texture(blockID, data) + blockmap[(blockID, data)] = generate_special_texture(blockID, data) if texture_size != 24: # rescale biome textures. biome_grass_texture = biome_grass_texture.resize(texture_dimensions, Image.ANTIALIAS) - # rescale the normal block images - for i in range(len(blockmap)): - if blockmap[i] != None: - block = blockmap[i] - alpha = block[1] - block = block[0] - block.putalpha(alpha) - scaled_block = block.resize(texture_dimensions, Image.ANTIALIAS) - blockmap[i] = generate_texture_tuple(scaled_block, i) - # rescale the special block images - for blockid, data in iter(specialblockmap): - block = specialblockmap[(blockid,data)] + for blockid, data in iter(blockmap): + block = blockmap[(blockid,data)] if block != None: - alpha = block[1] block = block[0] - block.putalpha(alpha) scaled_block = block.resize(texture_dimensions, Image.ANTIALIAS) - specialblockmap[(blockid,data)] = generate_texture_tuple(scaled_block, blockid) + blockmap[(blockid,data)] = generate_texture_tuple(scaled_block, blockid) From 4b905685ebc8b3bd6d18d1ae8ef64ac35ca537fe Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 31 Oct 2011 20:57:05 -0400 Subject: [PATCH 15/30] major textures.py restructuring, not all textures implemented (Issue #516) Be careful -- the build_* and transform_* functions no longer have a blockID argument, because that made no sense. --- overviewer_core/chunk.py | 2 +- overviewer_core/src/iterate.c | 4 - overviewer_core/textures.py | 2157 +++------------------------------ 3 files changed, 175 insertions(+), 1988 deletions(-) diff --git a/overviewer_core/chunk.py b/overviewer_core/chunk.py index e638350..06a2048 100644 --- a/overviewer_core/chunk.py +++ b/overviewer_core/chunk.py @@ -393,7 +393,7 @@ def generate_facemasks(): left = Image.new("L", (24,24), 0) whole = Image.new("L", (24,24), 0) - toppart = textures.transform_image(white) + toppart = textures.transform_image_top(white) leftpart = textures.transform_image_side(white) # using the real PIL paste here (not alpha_over) because there is diff --git a/overviewer_core/src/iterate.c b/overviewer_core/src/iterate.c index dac8587..7d8dc23 100644 --- a/overviewer_core/src/iterate.c +++ b/overviewer_core/src/iterate.c @@ -20,7 +20,6 @@ static PyObject *textures = NULL; static PyObject *chunk_mod = NULL; static PyObject *blockmap = NULL; -static PyObject *special_blocks = NULL; static PyObject *transparent_blocks = NULL; PyObject *init_chunk_render(PyObject *self, PyObject *args) { @@ -46,9 +45,6 @@ PyObject *init_chunk_render(PyObject *self, PyObject *args) { blockmap = PyObject_GetAttrString(textures, "blockmap"); if (!blockmap) return NULL; - special_blocks = PyObject_GetAttrString(textures, "special_blocks"); - if (!special_blocks) - return NULL; transparent_blocks = PyObject_GetAttrString(textures, "transparent_blocks"); if (!transparent_blocks) return NULL; diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 36af92a..586f481 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -24,11 +24,26 @@ from random import randint import numpy from PIL import Image, ImageEnhance, ImageOps, ImageDraw import logging +import functools import util import composite +## +## useful global variables +## + +# user-provided path given by --texture-path _find_file_local_path = None +# image background color to use +bgcolor = None +# an array of the textures in terrain.png, split up +terrain_images = None + +## +## Helpers for opening textures +## + def _find_file(filename, mode="rb", verbose=False): """Searches for the given file and returns an open handle to it. This searches the following locations in this order: @@ -106,9 +121,6 @@ def _load_image(filename): buffer = StringIO(fileobj.read()) return Image.open(buffer).convert("RGBA") -def _get_terrain_image(): - return _load_image("terrain.png") - def _split_terrain(terrain): """Builds and returns a length 256 array of each 16x16 chunk of texture""" textures = [] @@ -129,7 +141,11 @@ def _split_terrain(terrain): return textures -def transform_image(img, blockID=None): +## +## Image Transformation Functions +## + +def transform_image_top(img): """Takes a PIL image and rotates it left 45 degrees and shrinks the y axis by a factor of 2. Returns the resulting image, which will be 24x12 pixels @@ -157,25 +173,10 @@ def transform_image(img, blockID=None): newimg = img.transform((24,12), Image.AFFINE, transform) return newimg -def transform_image_side(img, blockID=None): +def transform_image_side(img): """Takes an image and shears it for the left side of the cube (reflect for the right side)""" - if blockID in (44,): # step block - # make the top half transparent - # (don't just crop img, since we want the size of - # img to be unchanged - mask = img.crop((0,8,16,16)) - n = Image.new(img.mode, img.size, bgcolor) - composite.alpha_over(n, mask,(0,0,16,8), mask) - img = n - if blockID in (78,): # snow - # make the top three quarters transparent - mask = img.crop((0,12,16,16)) - n = Image.new(img.mode, img.size, bgcolor) - composite.alpha_over(n, mask,(0,12,16,16), mask) - img = n - # Size of the cube side before shear img = img.resize((12,12), Image.ANTIALIAS) @@ -188,7 +189,7 @@ def transform_image_side(img, blockID=None): newimg = img.transform((12,18), Image.AFFINE, transform) return newimg -def transform_image_slope(img, blockID=None): +def transform_image_slope(img): """Takes an image and shears it in the shape of a slope going up in the -y direction (reflect for +x direction). Used for minetracks""" @@ -205,7 +206,7 @@ def transform_image_slope(img, blockID=None): return newimg -def transform_image_angle(img, angle, blockID=None): +def transform_image_angle(img, angle): """Takes an image an shears it in arbitrary angle with the axis of rotation being vertical. @@ -245,7 +246,7 @@ def transform_image_angle(img, angle, blockID=None): return newimg -def _build_block(top, side, blockID=None): +def build_block(top, side): """From a top texture and a side texture, build a block image. top and side should be 16x16 image objects. Returns a 24x24 image @@ -253,13 +254,13 @@ def _build_block(top, side, blockID=None): img = Image.new("RGBA", (24,24), bgcolor) original_texture = top.copy() - top = transform_image(top, blockID) + top = transform_image_top(top) if not side: composite.alpha_over(img, top, (0,0), top) return img - side = transform_image_side(side, blockID) + side = transform_image_side(side) otherside = side.transpose(Image.FLIP_LEFT_RIGHT) # Darken the sides slightly. These methods also affect the alpha layer, @@ -272,39 +273,9 @@ def _build_block(top, side, blockID=None): otherside = ImageEnhance.Brightness(otherside).enhance(0.8) otherside.putalpha(othersidealpha) - ## special case for tall-grass, fern, dead shrub, and pumpkin/melon stem - if blockID in (31,32,104,105): - front = original_texture.resize((14,11), Image.ANTIALIAS) - composite.alpha_over(img, front, (5,9)) - return img - - ## special case for non-block things - 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 - composite.alpha_over(img, side, (6,3), side) - composite.alpha_over(img, otherside, (6,3), otherside) - return img - - if blockID in (81,): # cacti! - 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) - composite.alpha_over(img, otherside, (12,12), otherside) - composite.alpha_over(img, top, (0,6), top) - elif blockID in (78,): # snow - # shift each texture down 9 pixels - composite.alpha_over(img, side, (0,6), side) - composite.alpha_over(img, otherside, (12,6), otherside) - composite.alpha_over(img, top, (0,9), top) - else: - composite.alpha_over(img, top, (0,0), top) - composite.alpha_over(img, side, (0,6), side) - composite.alpha_over(img, otherside, (12,6), otherside) + composite.alpha_over(img, top, (0,0), top) + composite.alpha_over(img, side, (0,6), side) + composite.alpha_over(img, otherside, (12,6), otherside) # Manually touch up 6 pixels that leave a gap because of how the # shearing works out. This makes the blocks perfectly tessellate-able @@ -317,8 +288,7 @@ def _build_block(top, side, blockID=None): return img - -def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None): +def build_full_block(top, side1, side2, side3, side4, bottom=None): """From a top texture, a bottom texture and 4 different side textures, build a full block with four differnts faces. All images should be 16x16 image objects. Returns a 24x24 image. Can be used to render any block. @@ -363,7 +333,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 = transform_image_side(side1) side1 = side1.transpose(Image.FLIP_LEFT_RIGHT) # Darken this side. @@ -375,7 +345,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None if side2 != None : - side2 = transform_image_side(side2, blockID) + side2 = transform_image_side(side2) # Darken this side. sidealpha2 = side2.split()[3] @@ -385,12 +355,12 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None composite.alpha_over(img, side2, (12,0), side2) if bottom != None : - bottom = transform_image(bottom, blockID) + bottom = transform_image_top(bottom) composite.alpha_over(img, bottom, (0,12), bottom) # front sides if side3 != None : - side3 = transform_image_side(side3, blockID) + side3 = transform_image_side(side3) # Darken this side sidealpha = side3.split()[3] @@ -400,7 +370,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None composite.alpha_over(img, side3, (0,6), side3) if side4 != None : - side4 = transform_image_side(side4, blockID) + side4 = transform_image_side(side4) side4 = side4.transpose(Image.FLIP_LEFT_RIGHT) # Darken this side @@ -411,107 +381,22 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None composite.alpha_over(img, side4, (12,6), side4) if top != None : - top = transform_image(top, blockID) + top = transform_image_top(top) composite.alpha_over(img, top, (0, increment), top) return img - -def _build_blockimages(): - """Returns a mapping from blockid to an image of that block in perspective - The values of the mapping are actually (image in RGB mode, alpha channel). - This is not appropriate for all block types, only block types that are - proper cubes""" - - # 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. - # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - 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, -1,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 - 55, -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, -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, -1, - # 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 - 66, 69, 72, 73, 75, -1,102,103,104,105,-1, 102, -1, -1, -1, -1, - # 96 97 98 99 100 101 102 103 - -1, -1, -1, -1, -1, -1, -1, 137, - ] - - # 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, -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, -1,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 - 55, -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, -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, -1, - # 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 - 66, 70, 72, 73, 74,-1 ,118,103,104,105, -1, 118,-1, -1, -1, -1, - # 96 97 98 99 100 101 102 103 - -1, -1, -1, -1, -1, -1, -1, 136, - ] - - # This maps block id to the texture that goes on the side of the block - if len(topids) != len(sideids): - raise Exception("mismatched lengths") - - allimages = [] - for toptextureid, sidetextureid,blockID in zip(topids, sideids,range(len(topids))): - if toptextureid == -1 or sidetextureid == -1: - allimages.append(None) - continue - - toptexture = terrain_images[toptextureid] - sidetexture = terrain_images[sidetextureid] - - ## _build_block needs to know about the block ID, not just the textures - ## of the block or the texture ID - img = _build_block(toptexture, sidetexture, blockID) - - allimages.append(generate_texture_tuple(img, blockID)) - - # Future block types: - while len(allimages) < 256: - allimages.append(None) - return allimages - -def load_water(): - """Evidentially, the water and lava textures are not loaded from any files - in the jar (that I can tell). They must be generated on the fly. While - terrain.png does have some water and lava cells, not all texture packs - include them. So I load them here from a couple pngs included. - - This mutates the blockmap global list with the new water and lava blocks. - Block 9, standing water, is given a block with only the top face showing. - Block 8, flowing water, is given a full 3 sided cube.""" - - watertexture = _load_image("water.png") - lavatexture = _load_image("lava.png") +def build_sprite(side): + """From a side texture, create a sprite-like texture such as those used + for spiderwebs or flowers.""" + img = Image.new("RGBA", (24,24), bgcolor) - w1 = _build_block(watertexture, None) - w1 = generate_texture_tuple(w1,9) - w2 = _build_block(watertexture, watertexture) - w2 = generate_texture_tuple(w2,8) + side = transform_image_side(side) + otherside = side.transpose(Image.FLIP_LEFT_RIGHT) - lavablock = _build_block(lavatexture, lavatexture) - lava = generate_texture_tuple(lavablock,10) - - for data in range(16): - blockmap[(9, data)] = w1 - blockmap[(8, data)] = w2 - - blockmap[(10, data)] = lava - blockmap[(11, data)] = lava + composite.alpha_over(img, side, (6,3), side) + composite.alpha_over(img, otherside, (6,3), otherside) + return img def generate_opaque_mask(img): """ Takes the alpha channel of the image and generates a mask @@ -521,1720 +406,27 @@ def generate_opaque_mask(img): alpha = img.split()[3] return alpha.point(lambda a: int(min(a, 25.5) * 10)) -def generate_texture_tuple(img, blockid): - """ This takes an image and returns the needed tuple for the - blockmap dictionary.""" - return (img, generate_opaque_mask(img)) - -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 - # 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: - global biome_grass_texture - composite.alpha_over(img, biome_grass_texture, (0, 0), biome_grass_texture) - return generate_texture_tuple(img, blockID) - - - 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 generate_texture_tuple(img, blockID) - - - if blockID == 9 or blockID == 20 or blockID == 79: # spring water, flowing water and waterfall water, AND glass, AND ice - # water,glass and ice share the way to be rendered - if blockID == 9: - texture = _load_image("water.png") - elif blockID == 20: - texture = terrain_images[49] - else: - texture = terrain_images[67] - - if (data & 0b10000) == 16: - top = texture - - else: top = None - - if (data & 0b0001) == 1: - side1 = texture # top left - else: side1 = None - - if (data & 0b1000) == 8: - side2 = texture # top right - else: side2 = None - - if (data & 0b0010) == 2: - side3 = texture # bottom left - else: side3 = None - - if (data & 0b0100) == 4: - side4 = texture # bottom right - else: side4 = None - - # if nothing shown do not draw at all - if top == side3 == side4 == None: - return None - - img = _build_full_block(top,None,None,side3,side4) - return generate_texture_tuple(img, blockID) - - - if blockID == 17: # wood: normal, birch and pines - top = terrain_images[21] - if data == 0: - side = terrain_images[20] - img = _build_block(top, side, 17) - if data == 1: - side = terrain_images[116] - img = _build_block(top, side, 17) - if data == 2: - side = terrain_images[117] - img = _build_block(top, side, 17) - - return generate_texture_tuple(img, blockID) - - - if blockID == 18: # leaves - t = terrain_images[52] - if data == 1: - # pine! - t = terrain_images[132] - img = _build_block(t, t, 18) - return generate_texture_tuple(img, blockID) - - - if blockID == 26: # bed - increment = 8 - 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) - - return generate_texture_tuple(img, blockID) - - - if blockID == 31: # tall grass - if data == 0: # dead shrub - texture = terrain_images[55] - elif data == 1: # tall grass - texture = terrain_images[39] - elif data == 2: # fern - texture = terrain_images[56] - - img = _build_block(texture, texture, blockID) - return generate_texture_tuple(img,31) - - - if blockID in (29,33): # sticky and normal body piston. - if blockID == 29: # sticky - piston_t = terrain_images[106].copy() - else: # normal - piston_t = terrain_images[107].copy() - - # other textures - side_t = terrain_images[108].copy() - back_t = terrain_images[109].copy() - interior_t = terrain_images[110].copy() - - if data & 0x08 == 0x08: # pushed out, non full blocks, tricky stuff - # remove piston texture from piston body - ImageDraw.Draw(side_t).rectangle((0, 0,16,3),outline=(0,0,0,0),fill=(0,0,0,0)) - - if data & 0x07 == 0x0: # down - side_t = side_t.rotate(180) - img = _build_full_block(back_t ,None ,None ,side_t, side_t) - - elif data & 0x07 == 0x1: # up - img = _build_full_block((interior_t, 4) ,None ,None ,side_t, side_t) - - elif data & 0x07 == 0x2: # east - img = _build_full_block(side_t , None, None ,side_t.rotate(90), back_t) - - elif data & 0x07 == 0x3: # west - img = _build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), None) - temp = transform_image_side(interior_t, blockID) - temp = temp.transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, temp, (9,5), temp) - - elif data & 0x07 == 0x4: # north - img = _build_full_block(side_t.rotate(90) ,None ,None , None, side_t.rotate(270)) - temp = transform_image_side(interior_t, blockID) - composite.alpha_over(img, temp, (3,5), temp) - - elif data & 0x07 == 0x5: # south - img = _build_full_block(side_t.rotate(270) ,None , None ,back_t, side_t.rotate(90)) - - else: # pushed in, normal full blocks, easy stuff - if data & 0x07 == 0x0: # down - side_t = side_t.rotate(180) - img = _build_full_block(back_t ,None ,None ,side_t, side_t) - elif data & 0x07 == 0x1: # up - img = _build_full_block(piston_t ,None ,None ,side_t, side_t) - elif data & 0x07 == 0x2: # east - img = _build_full_block(side_t ,None ,None ,side_t.rotate(90), back_t) - elif data & 0x07 == 0x3: # west - img = _build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), piston_t) - elif data & 0x07 == 0x4: # north - img = _build_full_block(side_t.rotate(90) ,None ,None ,piston_t, side_t.rotate(270)) - elif data & 0x07 == 0x5: # south - img = _build_full_block(side_t.rotate(270) ,None ,None ,back_t, side_t.rotate(90)) - - - return generate_texture_tuple(img, blockID) - - - if blockID == 34: # piston extension (sticky and normal) - if (data & 0x8) == 0x8: # sticky - piston_t = terrain_images[106].copy() - else: # normal - piston_t = terrain_images[107].copy() - - # other textures - side_t = terrain_images[108].copy() - back_t = terrain_images[107].copy() - # crop piston body - ImageDraw.Draw(side_t).rectangle((0, 4,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) - - # generate the horizontal piston extension stick - h_stick = Image.new("RGBA", (24,24), bgcolor) - temp = transform_image_side(side_t, blockID) - composite.alpha_over(h_stick, temp, (1,7), temp) - temp = transform_image(side_t.rotate(90)) - composite.alpha_over(h_stick, temp, (1,1), temp) - # Darken it - sidealpha = h_stick.split()[3] - h_stick = ImageEnhance.Brightness(h_stick).enhance(0.85) - h_stick.putalpha(sidealpha) - - # generate the vertical piston extension stick - v_stick = Image.new("RGBA", (24,24), bgcolor) - temp = transform_image_side(side_t.rotate(90), blockID) - composite.alpha_over(v_stick, temp, (12,6), temp) - temp = temp.transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(v_stick, temp, (1,6), temp) - # Darken it - sidealpha = v_stick.split()[3] - v_stick = ImageEnhance.Brightness(v_stick).enhance(0.85) - v_stick.putalpha(sidealpha) - - # Piston orientation is stored in the 3 first bits - if data & 0x07 == 0x0: # down - side_t = side_t.rotate(180) - img = _build_full_block((back_t, 12) ,None ,None ,side_t, side_t) - composite.alpha_over(img, v_stick, (0,-3), v_stick) - elif data & 0x07 == 0x1: # up - img = Image.new("RGBA", (24,24), bgcolor) - img2 = _build_full_block(piston_t ,None ,None ,side_t, side_t) - composite.alpha_over(img, v_stick, (0,4), v_stick) - composite.alpha_over(img, img2, (0,0), img2) - elif data & 0x07 == 0x2: # east - img = _build_full_block(side_t ,None ,None ,side_t.rotate(90), None) - temp = transform_image_side(back_t, blockID).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, temp, (2,2), temp) - composite.alpha_over(img, h_stick, (6,3), h_stick) - elif data & 0x07 == 0x3: # west - img = Image.new("RGBA", (24,24), bgcolor) - img2 = _build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), piston_t) - composite.alpha_over(img, h_stick, (0,0), h_stick) - composite.alpha_over(img, img2, (0,0), img2) - elif data & 0x07 == 0x4: # north - img = _build_full_block(side_t.rotate(90) ,None ,None , piston_t, side_t.rotate(270)) - composite.alpha_over(img, h_stick.transpose(Image.FLIP_LEFT_RIGHT), (0,0), h_stick.transpose(Image.FLIP_LEFT_RIGHT)) - elif data & 0x07 == 0x5: # south - img = Image.new("RGBA", (24,24), bgcolor) - img2 = _build_full_block(side_t.rotate(270) ,None ,None ,None, side_t.rotate(90)) - temp = transform_image_side(back_t, blockID) - composite.alpha_over(img2, temp, (10,2), temp) - composite.alpha_over(img, img2, (0,0), img2) - composite.alpha_over(img, h_stick.transpose(Image.FLIP_LEFT_RIGHT), (-3,2), h_stick.transpose(Image.FLIP_LEFT_RIGHT)) - - return generate_texture_tuple(img, blockID) - - - if blockID == 35: # wool - if data == 0: # white - top = side = terrain_images[64] - elif data == 1: # orange - top = side = terrain_images[210] - elif data == 2: # magenta - top = side = terrain_images[194] - elif data == 3: # light blue - top = side = terrain_images[178] - elif data == 4: # yellow - top = side = terrain_images[162] - elif data == 5: # light green - top = side = terrain_images[146] - elif data == 6: # pink - top = side = terrain_images[130] - elif data == 7: # grey - top = side = terrain_images[114] - elif data == 8: # light grey - top = side = terrain_images[225] - elif data == 9: # cyan - top = side = terrain_images[209] - elif data == 10: # purple - top = side = terrain_images[193] - elif data == 11: # blue - top = side = terrain_images[177] - elif data == 12: # brown - top = side = terrain_images[161] - elif data == 13: # dark green - top = side = terrain_images[145] - elif data == 14: # red - top = side = terrain_images[129] - elif data == 15: # black - top = side = terrain_images[113] - - img = _build_block(top, side, 35) - return generate_texture_tuple(img, blockID) - - - if blockID in (43,44): # slab and double-slab - - if data == 0: # stone slab - top = terrain_images[6] - side = terrain_images[5] - elif data == 1: # stone slab - top = terrain_images[176] - side = terrain_images[192] - elif data == 2: # wooden slab - top = side = terrain_images[4] - elif data == 3: # cobblestone slab - top = side = terrain_images[16] - elif data == 4: # brick? - top = side = terrain_images[7] - elif data == 5: # stone brick? - top = side = terrain_images[54] - - img = _build_block(top, side, blockID) - return generate_texture_tuple(img, blockID) - - - 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), bgcolor) - 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), bgcolor) - - 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, (7,5)) - composite.alpha_over(img, small_crop, (6,6)) - composite.alpha_over(img, small_crop, (7,6)) - composite.alpha_over(img, slice, (7,7)) - - return generate_texture_tuple(img, blockID) - - - 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), bgcolor) - - 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 generate_texture_tuple(img, blockID) - - - if blockID in (53,67, 108, 109): # wooden, stone brick, and cobblestone stairs. - - if blockID == 53: # wooden - texture = terrain_images[4] - elif blockID == 67: # cobblestone - texture = terrain_images[16] - elif blockID == 108: # red brick stairs - texture = terrain_images[7] - elif blockID == 109: # stone brick stairs - texture = terrain_images[54] - - side = texture.copy() - half_block_u = texture.copy() # up, down, left, right - half_block_d = texture.copy() - half_block_l = texture.copy() - half_block_r = texture.copy() - - # generate needed geometries - ImageDraw.Draw(side).rectangle((0,0,7,6),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(half_block_u).rectangle((0,8,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(half_block_d).rectangle((0,0,15,6),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(half_block_l).rectangle((8,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(half_block_r).rectangle((0,0,7,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - if data == 0: # ascending south - img = _build_full_block(half_block_r, None, None, half_block_d, side.transpose(Image.FLIP_LEFT_RIGHT)) - tmp1 = transform_image_side(half_block_u) - - # Darken the vertical part of the second step - sidealpha = tmp1.split()[3] - # darken it a bit more than usual, looks better - tmp1 = ImageEnhance.Brightness(tmp1).enhance(0.8) - tmp1.putalpha(sidealpha) - - composite.alpha_over(img, tmp1, (6,3)) - tmp2 = transform_image(half_block_l) - composite.alpha_over(img, tmp2, (0,6)) - - elif data == 1: # ascending north - img = Image.new("RGBA", (24,24), bgcolor) # first paste the texture in the back - tmp1 = transform_image(half_block_r) - composite.alpha_over(img, tmp1, (0,6)) - tmp2 = _build_full_block(half_block_l, None, None, texture, side) - composite.alpha_over(img, tmp2) - - elif data == 2: # ascending west - img = Image.new("RGBA", (24,24), bgcolor) # first paste the texture in the back - tmp1 = transform_image(half_block_u) - composite.alpha_over(img, tmp1, (0,6)) - tmp2 = _build_full_block(half_block_d, None, None, side, texture) - composite.alpha_over(img, tmp2) - - elif data == 3: # ascending east - img = _build_full_block(half_block_u, None, None, side.transpose(Image.FLIP_LEFT_RIGHT), half_block_d) - tmp1 = transform_image_side(half_block_u).transpose(Image.FLIP_LEFT_RIGHT) - - # Darken the vertical part of the second step - sidealpha = tmp1.split()[3] - # darken it a bit more than usual, looks better - tmp1 = ImageEnhance.Brightness(tmp1).enhance(0.7) - tmp1.putalpha(sidealpha) - - composite.alpha_over(img, tmp1, (6,3)) - tmp2 = transform_image(half_block_d) - composite.alpha_over(img, tmp2, (0,6)) - - # touch up a (horrible) pixel - img.putpixel((18,3),(0,0,0,0)) - - return generate_texture_tuple(img, blockID) - - 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 generate_texture_tuple(img, blockID) - - - 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), bgcolor) - 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 generate_texture_tuple(img, blockID) - - - 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 generate_texture_tuple(img, blockID) - - - 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), bgcolor) - 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 generate_texture_tuple(img, blockID) - - - if blockID in (61, 62, 23): #furnace and burning furnace - top = terrain_images[62] - side = terrain_images[45] - - if blockID == 61: - front = terrain_images[44] - - elif blockID == 62: - front = terrain_images[45+16] - - elif blockID == 23: - front = terrain_images[46] - - if data == 3: # pointing west - img = _build_full_block(top, None, None, side, front) - - 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) - - return generate_texture_tuple(img, blockID) - - - if blockID == 63: # singposts - - texture = terrain_images[4].copy() - # cut the planks to the size of a signpost - ImageDraw.Draw(texture).rectangle((0,12,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - # If the signpost is looking directly to the image, draw some - # random dots, they will look as text. - if data in (0,1,2,3,4,5,15): - for i in range(15): - x = randint(4,11) - y = randint(3,7) - texture.putpixel((x,y),(0,0,0,255)) - - # Minecraft uses wood texture for the signpost stick - texture_stick = terrain_images[20] - texture_stick = texture_stick.resize((12,12), Image.ANTIALIAS) - ImageDraw.Draw(texture_stick).rectangle((2,0,12,12),outline=(0,0,0,0),fill=(0,0,0,0)) - - img = Image.new("RGBA", (24,24), bgcolor) - - # W N ~90 E S ~270 - angles = (330.,345.,0.,15.,30.,55.,95.,120.,150.,165.,180.,195.,210.,230.,265.,310.) - angle = math.radians(angles[data]) - post = transform_image_angle(texture, angle) - - # choose the position of the "3D effect" - incrementx = 0 - if data in (1,6,7,8,9,14): - incrementx = -1 - elif data in (3,4,5,11,12,13): - incrementx = +1 - - composite.alpha_over(img, texture_stick,(11, 8),texture_stick) - # post2 is a brighter signpost pasted with a small sift, - # gives to the signpost some 3D effect. - post2 = ImageEnhance.Brightness(post).enhance(1.2) - composite.alpha_over(img, post2,(incrementx, -3),post2) - composite.alpha_over(img, post, (0,-2), post) - - return generate_texture_tuple(img, blockID) - - - 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), bgcolor) - if (data & 0x03) == 0: # northeast corner - 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: # southeast corner - 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: # southwest corner - 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: # northwest corner - 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 generate_texture_tuple(img, blockID) - - - if blockID == 65: # ladder - img = Image.new("RGBA", (24,24), bgcolor) - 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) - composite.alpha_over(img, tex, (0,6), tex) - return generate_texture_tuple(img, blockID) - if data == 2: - tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (12,6), tex) - return generate_texture_tuple(img, blockID) - if data == 3: - tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (0,0), tex) - return generate_texture_tuple(img, blockID) - if data == 4: - tex = transform_image_side(raw_texture) - composite.alpha_over(img, tex, (12,0), tex) - return generate_texture_tuple(img, blockID) - - - if blockID in (27, 28, 66): # minetrack: - img = Image.new("RGBA", (24,24), bgcolor) - - 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] - - ## use transform_image to scale and shear - if data == 0: - track = transform_image(raw_straight, blockID) - composite.alpha_over(img, track, (0,12), track) - elif data == 6: - track = transform_image(raw_corner, blockID) - composite.alpha_over(img, track, (0,12), track) - elif data == 7: - track = transform_image(raw_corner.rotate(270), blockID) - composite.alpha_over(img, track, (0,12), track) - elif data == 8: - # flip - track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90), - blockID) - composite.alpha_over(img, track, (0,12), track) - elif data == 9: - track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM), - blockID) - composite.alpha_over(img, track, (0,12), track) - elif data == 1: - track = transform_image(raw_straight.rotate(90), blockID) - composite.alpha_over(img, track, (0,12), track) - - #slopes - elif data == 2: # slope going up in +x direction - track = transform_image_slope(raw_straight,blockID) - track = track.transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, track, (2,0), track) - # the 2 pixels move is needed to fit with the adjacent tracks - - 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. - 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! - elif data == 4: # slope going up in -y direction - track = transform_image_slope(raw_straight,blockID) - composite.alpha_over(img, track, (0,0), track) - - elif data == 5: # slope going up in +y direction - # same as "data == 3" - ImageDraw.Draw(img).line([(1,17),(12,11)],fill=(164,164,164)) - - return generate_texture_tuple(img, blockID) - - - if blockID == 68: # wall sign - texture = terrain_images[4].copy() - # cut the planks to the size of a signpost - ImageDraw.Draw(texture).rectangle((0,12,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - # draw some random black dots, they will look as text - """ don't draw text at the moment, they are used in blank for decoration - - if data in (3,4): - for i in range(15): - x = randint(4,11) - y = randint(3,7) - texture.putpixel((x,y),(0,0,0,255)) - """ - - img = Image.new("RGBA", (24,24), bgcolor) - - incrementx = 0 - if data == 2: # east - incrementx = +1 - sign = _build_full_block(None, None, None, None, texture) - elif data == 3: # west - incrementx = -1 - sign = _build_full_block(None, texture, None, None, None) - elif data == 4: # north - incrementx = +1 - sign = _build_full_block(None, None, texture, None, None) - elif data == 5: # south - incrementx = -1 - sign = _build_full_block(None, None, None, texture, None) - - sign2 = ImageEnhance.Brightness(sign).enhance(1.2) - composite.alpha_over(img, sign2,(incrementx, 2),sign2) - composite.alpha_over(img, sign, (0,3), sign) - - return generate_texture_tuple(img, blockID) - - if blockID == 70 or blockID == 72: # wooden and stone pressure plates - if blockID == 70: # stone - t = terrain_images[1].copy() - else: # wooden - t = terrain_images[4].copy() - - # cut out the outside border, pressure plates are smaller - # than a normal block - ImageDraw.Draw(t).rectangle((0,0,15,15),outline=(0,0,0,0)) - - # create the textures and a darker version to make a 3d by - # pasting them with an offstet of 1 pixel - img = Image.new("RGBA", (24,24), bgcolor) - - top = transform_image(t, blockID) - - alpha = top.split()[3] - topd = ImageEnhance.Brightness(top).enhance(0.8) - topd.putalpha(alpha) - - #show it 3d or 2d if unpressed or pressed - if data == 0: - composite.alpha_over(img,topd, (0,12),topd) - composite.alpha_over(img,top, (0,11),top) - elif data == 1: - composite.alpha_over(img,top, (0,12),top) - - return generate_texture_tuple(img, blockID) - - if blockID == 85: # fences - # create needed images for Big stick fence - - fence_top = terrain_images[4].copy() - fence_side = terrain_images[4].copy() - - # generate the textures of the fence - ImageDraw.Draw(fence_top).rectangle((0,0,5,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(fence_top).rectangle((10,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(fence_top).rectangle((0,0,15,5),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(fence_top).rectangle((0,10,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - ImageDraw.Draw(fence_side).rectangle((0,0,5,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(fence_side).rectangle((10,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - # Create the sides and the top of the big stick - fence_side = transform_image_side(fence_side,85) - fence_other_side = fence_side.transpose(Image.FLIP_LEFT_RIGHT) - fence_top = transform_image(fence_top,85) - - # Darken the sides slightly. These methods also affect the alpha layer, - # so save them first (we don't want to "darken" the alpha layer making - # the block transparent) - sidealpha = fence_side.split()[3] - fence_side = ImageEnhance.Brightness(fence_side).enhance(0.9) - fence_side.putalpha(sidealpha) - othersidealpha = fence_other_side.split()[3] - fence_other_side = ImageEnhance.Brightness(fence_other_side).enhance(0.8) - fence_other_side.putalpha(othersidealpha) - - # Compose the fence big stick - fence_big = Image.new("RGBA", (24,24), bgcolor) - 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,0),fence_top) - - # Now render the small sticks. - # Create needed images - fence_small_side = terrain_images[4].copy() - - # Generate mask - ImageDraw.Draw(fence_small_side).rectangle((0,0,15,0),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(fence_small_side).rectangle((0,4,15,6),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(fence_small_side).rectangle((0,10,15,16),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(fence_small_side).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(fence_small_side).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - # Create the sides and the top of the small sticks - fence_small_side = transform_image_side(fence_small_side,85) - fence_small_other_side = fence_small_side.transpose(Image.FLIP_LEFT_RIGHT) - - # Darken the sides slightly. These methods also affect the alpha layer, - # so save them first (we don't want to "darken" the alpha layer making - # the block transparent) - sidealpha = fence_small_other_side.split()[3] - fence_small_other_side = ImageEnhance.Brightness(fence_small_other_side).enhance(0.9) - fence_small_other_side.putalpha(sidealpha) - sidealpha = fence_small_side.split()[3] - fence_small_side = ImageEnhance.Brightness(fence_small_side).enhance(0.9) - fence_small_side.putalpha(sidealpha) - - # Create img to compose the fence - img = Image.new("RGBA", (24,24), bgcolor) - - # Position of fence small sticks in img. - # These postitions are strange because the small sticks of the - # fence are at the very left and at the very right of the 16x16 images - pos_top_left = (2,3) - pos_top_right = (10,3) - pos_bottom_right = (10,7) - pos_bottom_left = (2,7) - - # +x axis points top right direction - # +y axis points bottom right direction - # First compose small sticks in the back of the image, - # then big stick and thecn small sticks in the front. - - if (data & 0b0001) == 1: - composite.alpha_over(img,fence_small_side, pos_top_left,fence_small_side) # top left - if (data & 0b1000) == 8: - composite.alpha_over(img,fence_small_other_side, pos_top_right,fence_small_other_side) # top right - - composite.alpha_over(img,fence_big,(0,0),fence_big) - - if (data & 0b0010) == 2: - composite.alpha_over(img,fence_small_other_side, pos_bottom_left,fence_small_other_side) # bottom left - if (data & 0b0100) == 4: - composite.alpha_over(img,fence_small_side, pos_bottom_right,fence_small_side) # bottom right - - return generate_texture_tuple(img, blockID) - - - if blockID in (86,91): # pumpkins, jack-o-lantern - top = terrain_images[102] - frontID = 119 if blockID == 86 else 120 - 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 generate_texture_tuple(img, blockID) - - - if blockID == 90: # portal - portaltexture = _load_image("portal.png") - img = Image.new("RGBA", (24,24), bgcolor) - - 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 generate_texture_tuple(img, blockID) - - - if blockID == 92: # cake! (without bites, at the moment) - - top = terrain_images[121] - side = terrain_images[122] - top = transform_image(top, blockID) - side = transform_image_side(side, blockID) - otherside = side.transpose(Image.FLIP_LEFT_RIGHT) - - sidealpha = side.split()[3] - side = ImageEnhance.Brightness(side).enhance(0.9) - side.putalpha(sidealpha) - othersidealpha = otherside.split()[3] - otherside = ImageEnhance.Brightness(otherside).enhance(0.8) - otherside.putalpha(othersidealpha) - - img = Image.new("RGBA", (24,24), bgcolor) - - composite.alpha_over(img, side, (1,6), side) - composite.alpha_over(img, otherside, (11,7), otherside) # workaround, fixes a hole - composite.alpha_over(img, otherside, (12,6), otherside) - composite.alpha_over(img, top, (0,6), top) - - return generate_texture_tuple(img, blockID) - - - if blockID in (93, 94): # redstone repeaters (diodes), ON and OFF - # generate the diode - top = terrain_images[131] if blockID == 93 else terrain_images[147] - side = terrain_images[5] - increment = 13 - - 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) - - # compose a "3d" redstone torch - t = terrain_images[115].copy() if blockID == 93 else terrain_images[99].copy() - torch = Image.new("RGBA", (24,24), bgcolor) - - t_crop = t.crop((2,2,14,14)) - slice = t_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(torch, slice, (6,4)) - composite.alpha_over(torch, t_crop, (5,5)) - composite.alpha_over(torch, t_crop, (6,5)) - composite.alpha_over(torch, slice, (6,6)) - - # paste redstone torches everywhere! - # 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 = (0,2) - static_torch = (5,-1) - - elif (data & 0xC) == 8: # three ticks delay - moving_torch = (-1,2) - static_torch = (5,-1) - - elif (data & 0xC) == 12: # four ticks delay - moving_torch = (-2,3) - 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) - - return generate_texture_tuple(img, blockID) - - - if blockID == 96: # trapdoor - texture = terrain_images[84] - if data & 0x4 == 0x4: # opened trapdoor - if data & 0x3 == 0: # west - img = _build_full_block(None, None, None, None, texture) - if data & 0x3 == 1: # east - img = _build_full_block(None, texture, None, None, None) - if data & 0x3 == 2: # south - img = _build_full_block(None, None, texture, None, None) - if data & 0x3 == 3: # north - img = _build_full_block(None, None, None, texture, None) - - elif data & 0x4 == 0: # closed trapdoor - img = _build_full_block((texture, 12), None, None, texture, texture) - - return generate_texture_tuple(img, blockID) - - if blockID == 98: # normal, mossy and cracked stone brick - if data == 0: # normal - t = terrain_images[54] - elif data == 1: # mossy - t = terrain_images[100] - else: # cracked - t = terrain_images[101] - - img = _build_full_block(t, None, None, t, t) - - return generate_texture_tuple(img, blockID) - - if blockID == 99 or blockID == 100: # huge brown and red mushroom - if blockID == 99: # brown - cap = terrain_images[126] - else: # red - cap = terrain_images[125] - stem = terrain_images[141] - porous = terrain_images[142] - - if data == 0: # fleshy piece - img = _build_full_block(porous, None, None, porous, porous) - - if data == 1: # north-east corner - img = _build_full_block(cap, None, None, cap, porous) - - if data == 2: # east side - img = _build_full_block(cap, None, None, porous, porous) - - if data == 3: # south-east corner - img = _build_full_block(cap, None, None, porous, cap) - - if data == 4: # north side - img = _build_full_block(cap, None, None, cap, porous) - - if data == 5: # top piece - img = _build_full_block(cap, None, None, porous, porous) - - if data == 6: # south side - img = _build_full_block(cap, None, None, cap, porous) - - if data == 7: # north-west corner - img = _build_full_block(cap, None, None, cap, cap) - - if data == 8: # west side - img = _build_full_block(cap, None, None, porous, cap) - - if data == 9: # south-west corner - img = _build_full_block(cap, None, None, porous, cap) - - if data == 10: # stem - img = _build_full_block(porous, None, None, stem, stem) - - return generate_texture_tuple(img, blockID) - - if blockID == 101 or blockID == 102: # iron bars and glass panes - if blockID == 101: - # iron bars - t = terrain_images[85] - else: - # glass panes - t = terrain_images[49] - left = t.copy() - right = t.copy() - - # generate the four small pieces of the glass pane - ImageDraw.Draw(right).rectangle((0,0,7,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(left).rectangle((8,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - up_left = transform_image_side(left) - up_right = transform_image_side(right).transpose(Image.FLIP_TOP_BOTTOM) - dw_right = transform_image_side(right) - dw_left = transform_image_side(left).transpose(Image.FLIP_TOP_BOTTOM) - - # Create img to compose the texture - img = Image.new("RGBA", (24,24), bgcolor) - - # +x axis points top right direction - # +y axis points bottom right direction - # First compose things in the back of the image, - # then things in the front. - - if (data & 0b0001) == 1 or data == 0: - composite.alpha_over(img,up_left, (6,3),up_left) # top left - if (data & 0b1000) == 8 or data == 0: - composite.alpha_over(img,up_right, (6,3),up_right) # top right - if (data & 0b0010) == 2 or data == 0: - composite.alpha_over(img,dw_left, (6,3),dw_left) # bottom left - if (data & 0b0100) == 4 or data == 0: - composite.alpha_over(img,dw_right, (6,3),dw_right) # bottom right - - return generate_texture_tuple(img, blockID) - - if blockID == 104 or blockID == 105: # pumpkin and melon stems. - # the ancildata value indicates how much of the texture - # is shown. - if data & 48 == 0: - # not fully grown stem or no pumpkin/melon touching it, - # straight up stem - t = terrain_images[111].copy() - img = Image.new("RGBA", (16,16), bgcolor) - composite.alpha_over(img, t, (0, int(16 - 16*((data + 1)/8.))), t) - img = _build_block(img, img, blockID) - if data & 7 == 7: - # fully grown stem gets brown color! - # there is a conditional in rendermode-normal to not - # tint the data value 7 - img = tintTexture(img, (211,169,116)) - return generate_texture_tuple(img, blockID) - - else: # fully grown, and a pumpking/melon touching it, - # corner stem - pass - - - if blockID == 106: # vine - img = Image.new("RGBA", (24,24), bgcolor) - raw_texture = terrain_images[143] - # print "vine is facing: %d" % data - if data == 2: # south - tex = transform_image_side(raw_texture) - composite.alpha_over(img, tex, (0,6), tex) - return generate_texture_tuple(img, blockID) - if data == 1: # east - tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (12,6), tex) - return generate_texture_tuple(img, blockID) - if data == 4: # west - tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (0,0), tex) - return generate_texture_tuple(img, blockID) - if data == 8: # north - tex = transform_image_side(raw_texture) - composite.alpha_over(img, tex, (12,0), tex) - return generate_texture_tuple(img, blockID) - - if blockID == 107: - # create the closed gate side - gate_side = terrain_images[4].copy() - gate_side_draw = ImageDraw.Draw(gate_side) - gate_side_draw.rectangle((7,0,15,0),outline=(0,0,0,0),fill=(0,0,0,0)) - gate_side_draw.rectangle((7,4,9,6),outline=(0,0,0,0),fill=(0,0,0,0)) - gate_side_draw.rectangle((7,10,15,16),outline=(0,0,0,0),fill=(0,0,0,0)) - gate_side_draw.rectangle((0,12,15,16),outline=(0,0,0,0),fill=(0,0,0,0)) - gate_side_draw.rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) - gate_side_draw.rectangle((14,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - # darken the sides slightly, as with the fences - sidealpha = gate_side.split()[3] - gate_side = ImageEnhance.Brightness(gate_side).enhance(0.9) - gate_side.putalpha(sidealpha) - - # create the other sides - mirror_gate_side = transform_image_side(gate_side.transpose(Image.FLIP_LEFT_RIGHT), blockID) - gate_side = transform_image_side(gate_side, blockID) - gate_other_side = gate_side.transpose(Image.FLIP_LEFT_RIGHT) - mirror_gate_other_side = mirror_gate_side.transpose(Image.FLIP_LEFT_RIGHT) - - # Create img to compose the fence gate - img = Image.new("RGBA", (24,24), bgcolor) - - if data & 0x4: - # opened - data = data & 0x3 - if data == 0: - composite.alpha_over(img, gate_side, (2,8), gate_side) - composite.alpha_over(img, gate_side, (13,3), gate_side) - elif data == 1: - composite.alpha_over(img, gate_other_side, (-1,3), gate_other_side) - composite.alpha_over(img, gate_other_side, (10,8), gate_other_side) - elif data == 2: - composite.alpha_over(img, mirror_gate_side, (-1,7), mirror_gate_side) - composite.alpha_over(img, mirror_gate_side, (10,2), mirror_gate_side) - elif data == 3: - composite.alpha_over(img, mirror_gate_other_side, (2,1), mirror_gate_other_side) - composite.alpha_over(img, mirror_gate_other_side, (13,7), mirror_gate_other_side) - else: - # closed - - # positions for pasting the fence sides, as with fences - pos_top_left = (2,3) - pos_top_right = (10,3) - pos_bottom_right = (10,7) - pos_bottom_left = (2,7) - - if data == 0 or data == 2: - composite.alpha_over(img, gate_other_side, pos_top_right, gate_other_side) - composite.alpha_over(img, mirror_gate_other_side, pos_bottom_left, mirror_gate_other_side) - elif data == 1 or data == 3: - composite.alpha_over(img, gate_side, pos_top_left, gate_side) - composite.alpha_over(img, mirror_gate_side, pos_bottom_right, mirror_gate_side) - - return generate_texture_tuple(img, blockID) - - 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,108,109): # 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 - if blockID == 99 or blockID == 100: # huge red and brown mushroom - if _north == 'upper-left': - if data == 1: data = 3 - elif data == 2: data = 6 - elif data == 3: data = 9 - elif data == 4: data = 2 - elif data == 6: data = 8 - elif data == 7: data = 1 - elif data == 8: data = 4 - elif data == 9: data = 7 - elif _north == 'upper-right': - if data == 1: data = 9 - elif data == 2: data = 8 - elif data == 3: data = 7 - elif data == 4: data = 6 - elif data == 6: data = 4 - elif data == 7: data = 3 - elif data == 8: data = 2 - elif data == 9: data = 1 - elif _north == 'lower-right': - if data == 1: data = 7 - elif data == 2: data = 4 - elif data == 3: data = 1 - elif data == 4: data = 2 - elif data == 6: data = 8 - elif data == 7: data = 9 - elif data == 8: data = 6 - elif data == 9: data = 3 - if blockID == 106: # vine - if _north == 'upper-left': - if data == 1: data = 2 - elif data == 4: data = 8 - elif data == 8: data = 1 - elif data == 2: data = 4 - elif _north == 'upper-right': - if data == 1: data = 4 - elif data == 4: data = 1 - elif data == 8: data = 2 - elif data == 2: data = 8 - elif _north == 'lower-right': - if data == 1: data = 8 - elif data == 4: data = 2 - elif data == 8: data = 4 - elif data == 2: data = 1 - if blockID == 107: # fence gates - opened = False - if data & 0x4: - data = data & 0x3 - opened = True - 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 opened: - data = data | 0x4 - - return data - def tintTexture(im, c): # apparently converting to grayscale drops the alpha channel? i = ImageOps.colorize(ImageOps.grayscale(im), (0,0,0), c) i.putalpha(im.split()[3]); # copy the alpha band back in. assuming RGBA return i +def generate_texture_tuple(img): + """ This takes an image and returns the needed tuple for the + blockmap dictionary.""" + return (img, generate_opaque_mask(img)) + +## +## Biomes +## + currentBiomeFile = None currentBiomeData = None grasscolor = None foliagecolor = None watercolor = None +_north = None def prepareBiomeData(worlddir): global grasscolor, foliagecolor, watercolor @@ -2275,6 +467,7 @@ def getBiomeData(worlddir, chunkX, chunkY): ''' global currentBiomeFile, currentBiomeData + global _north biomeX = chunkX // 32 biomeY = chunkY // 32 rots = 0 @@ -2314,6 +507,10 @@ def getBiomeData(worlddir, chunkX, chunkY): currentBiomeData = data return data +## +## Color Light +## + lightcolor = None lightcolor_checked = False def loadLightColor(): @@ -2328,143 +525,99 @@ def loadLightColor(): lightcolor = None return lightcolor -# This set holds blocks ids that can be seen through, for occlusion calculations -transparent_blocks = set([ 0, 6, 8, 9, 18, 20, 26, 27, 28, 29, 30, 31, 32, 33, - 34, 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, 96, 101, 102, 104, 105, - 106, 107, 108, 109]) - -# 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, 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]) - -# This set holds block ids that are not candidates for spawning mobs on -# (glass, slabs, stairs, fluids, ice, pistons, webs,TNT, wheat, cactus, iron bars, glass planes, fences, fence gate, cake, bed, repeaters, trapdoor) -nospawn_blocks = set([20,26, 29, 30, 33, 34, 44, 46, 53, 59, 67, 79, 81, 85, 92, 93, 94, 96, 107, 109, 101, 102]).union(fluid_blocks) - -# 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([ 2, 6, 9, 17, 18, 20, 26, 23, 27, 28, 29, 31, 33, - 34, 35, 43, 44, 50, 51, 53, 54, 55, 58, 59, 61, 62, - 63, 64, 65, 66, 67, 68, 70, 71, 72, 75, 76, 79, 85, - 86, 90, 91, 92, 93, 94, 96, 98, 99, 100, 101, 102, - 104, 105, 106, 107, 108, 109]) - -# this is a map of special blockIDs to a list of all -# possible values for ancillary data that it might have. - -special_map = {} - -# 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[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(3) # wood: normal, birch and pine -special_map[18] = range(16) # leaves, birch, normal or pine leaves -special_map[20] = range(32) # glass, used to only render the exterior surface, uses pseudo data -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[29] = (0,1,2,3,4,5,8,9,10,11,12,13) # sticky piston body, orientation, pushed in/out -special_map[31] = range(3) # tall grass, dead shrub, fern and tall grass itself -special_map[33] = (0,1,2,3,4,5,8,9,10,11,12,13) # normal piston body, orientation, pushed in/out -special_map[34] = (0,1,2,3,4,5,8,9,10,11,12,13) # normal and sticky piston extension, orientation, sticky/normal -special_map[35] = range(16) # wool, colored and white -special_map[43] = range(6) # stone, sandstone, wooden and cobblestone double-slab -special_map[44] = range(6) # 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[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, it has 2 different sides -special_map[59] = range(8) # crops, grow from 0 to 7 -special_map[61] = range(6) # furnace, orientation -special_map[62] = range(6) # burning furnace, orientation -special_map[63] = range(16) # signpost, 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 -special_map[67] = range(4) # cobblestone stairs, orientation -special_map[68] = (2,3,4,5) # wall sing, orientation -special_map[70] = (0,1) # stone pressure plate, non pressed and pressed -special_map[71] = range(16) # iron door, open/close and orientation -special_map[72] = (0,1) # wooden pressure plate, non pressed and pressed -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[79] = range(32) # ice, used to only render the exterior surface, uses pseudo data -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, eaten amount, (not implemented) -special_map[93] = range(16) # OFF redstone repeater, orientation and delay -special_map[94] = range(16) # ON redstone repeater, orientation and delay -special_map[96] = range(8) # trapdoor, open, closed, orientation -special_map[98] = range(3) # stone brick, normal, mossy and cracked -special_map[99] = range(11) # huge brown mushroom, side, corner, etc, piece -special_map[100] = range(11) # huge red mushroom, side, corner, etc, piece -special_map[101]= range(16) # iron bars, all the possible combination, uses pseudo data -special_map[102]= range(16) # glass panes, all the possible combination, uses pseudo data -special_map[104] = range(8) # pumpkin stem, size of the stem -special_map[105] = range(8) # melon stem, size of the stem -special_map[106] = (1,2,4,8) # vine, orientation -special_map[107] = range(8) # fence gates, orientation + open bit -special_map[108]= range(4) # red stairs, orientation -special_map[109]= range(4) # stonebrick stairs, orientation +## +## The big one: generate() and associated framework +## # placeholders that are generated in generate() -bgcolor = None -terrain_images = None -blockmap = None +texture_dimensions = None +blockmap_generators = {} +blockmap = {} biome_grass_texture = None +transparent_blocks = set([0,]) +solid_blocks = set() +fluid_blocks = set() +nospawn_blocks = set() + +# the material registration decorator +def material(blockIds, data=[0], **kwargs): + # mapping from property name to the set to store them in + properties = {"transparent" : transparent_blocks, "solid" : solid_blocks, "fluid" : fluid_blocks, "nospawn" : nospawn_blocks} + + # make sure blockIds and data are iterable + try: + blockIds = iter(blockIds) + except: + blockIds = [blockIds,] + try: + data = iter(data) + except: + data = [data,] + + def inner_material(func): + global blockmap_generators + + # create a wrapper function with a known signature + @functools.wraps(func) + def func_wrapper(blockId, data, north): + try: + return func(blockId, data, north) + except TypeError: + return func(blockId, data) + + for block in blockIds: + # set the property sets appropriately + for prop in properties: + if kwargs.get(prop, False): + properties[prop].update([block]) + + # populate blockmap_generators with our function + for d in data: + blockmap_generators[(block, d)] = func_wrapper + + return func_wrapper + return inner_material + +# shortcut function for pure blocks, default to solid +def block(blockid, top_index, side_index=None, **kwargs): + new_kwargs = {'solid' : True} + new_kwargs.update(kwargs) + + if side_index is None: + side_index = top_index + + @material(blockid, **new_kwargs) + def inner_block(unused_id, unused_data): + return build_block(terrain_images[top_index], terrain_images[side_index]) + return inner_block + 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 + global texture_dimensions + global _north bgcolor = bgc - global _find_file_local_path, texture_dimensions _find_file_local_path = path + _north = north_direction texture_dimensions = (texture_size, texture_size) # This maps terainids to 16x16 images global terrain_images - terrain_images = _split_terrain(_get_terrain_image()) - - # generate the normal blocks - global blockmap - blockmap = {} - for blockID, t in enumerate(_build_blockimages()): - blockmap[(blockID, 0)] = t - - load_water() + terrain_images = _split_terrain(_load_image("terrain.png")) # generate biome grass mask global biome_grass_texture - biome_grass_texture = _build_block(terrain_images[0], terrain_images[38], 2) - - # generate the special blocks - global special_blocks - for blockID in special_blocks: - for data in special_map[blockID]: - blockmap[(blockID, data)] = generate_special_texture(blockID, data) + biome_grass_texture = build_block(terrain_images[0], terrain_images[38]) + # generate the blocks + global blockmap, blockmap_generators + blockmap = {} + for blockid, data in blockmap_generators: + texgen = blockmap_generators[(blockid, data)] + tex = texgen(blockid, data, north_direction) + blockmap[(blockid, data)] = generate_texture_tuple(tex) + if texture_size != 24: # rescale biome textures. biome_grass_texture = biome_grass_texture.resize(texture_dimensions, Image.ANTIALIAS) @@ -2476,3 +629,41 @@ def generate(path=None,texture_size=24,bgc = (26,26,26,0),north_direction='lower block = block[0] scaled_block = block.resize(texture_dimensions, Image.ANTIALIAS) blockmap[(blockid,data)] = generate_texture_tuple(scaled_block, blockid) + +## +## and finally: actual texture definitions +## + +# stone +block(1, 1) + +@material(2, range(11) + [0x10,], solid=True) +def grass(blockid, data): + # 0x10 bit means SNOW + side_img = terrain_images[3] + if data & 0x10: + side_img = terrain_images[68] + img = build_block(terrain_images[0], side_img) + if not data & 0x10: + global biome_grass_texture + composite.alpha_over(img, biome_grass_texture, (0, 0), biome_grass_texture) + return img + +# dirt +block(3, 2) +# cobblestone +block(4, 16) +# wooden plank +block(5, 4) + +@material(6, range(16), transparent=True) +def saplings(blockid, data): + # usual saplings + tex = terrain_images[15] + + if data & 0x3 == 1: # spruce sapling + tex = terrain_images[63] + if data & 0x3 == 2: # birch sapling + tex = terrain_images[79] + + return build_sprite(tex) From 7a92343bb8d7ec5d37b488011b1a376fc5a405b2 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Nov 2011 15:51:10 -0400 Subject: [PATCH 16/30] @material and block have been made more verbose (Issue #516) --- overviewer_core/textures.py | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 586f481..0609789 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -541,15 +541,15 @@ fluid_blocks = set() nospawn_blocks = set() # the material registration decorator -def material(blockIds, data=[0], **kwargs): +def material(blockid=[], data=[0], **kwargs): # mapping from property name to the set to store them in properties = {"transparent" : transparent_blocks, "solid" : solid_blocks, "fluid" : fluid_blocks, "nospawn" : nospawn_blocks} - # make sure blockIds and data are iterable + # make sure blockid and data are iterable try: - blockIds = iter(blockIds) + blockid = iter(blockid) except: - blockIds = [blockIds,] + blockid = [blockid,] try: data = iter(data) except: @@ -560,13 +560,13 @@ def material(blockIds, data=[0], **kwargs): # create a wrapper function with a known signature @functools.wraps(func) - def func_wrapper(blockId, data, north): + def func_wrapper(blockid, data, north): try: - return func(blockId, data, north) + return func(blockid, data, north) except TypeError: - return func(blockId, data) + return func(blockid, data) - for block in blockIds: + for block in blockid: # set the property sets appropriately for prop in properties: if kwargs.get(prop, False): @@ -580,14 +580,17 @@ def material(blockIds, data=[0], **kwargs): return inner_material # shortcut function for pure blocks, default to solid -def block(blockid, top_index, side_index=None, **kwargs): +def block(blockid=[], top_index=None, side_index=None, **kwargs): new_kwargs = {'solid' : True} new_kwargs.update(kwargs) + if top_index is None: + raise ValueError("top_index was not provided") + if side_index is None: side_index = top_index - @material(blockid, **new_kwargs) + @material(blockid=blockid, **new_kwargs) def inner_block(unused_id, unused_data): return build_block(terrain_images[top_index], terrain_images[side_index]) return inner_block @@ -635,9 +638,9 @@ def generate(path=None,texture_size=24,bgc = (26,26,26,0),north_direction='lower ## # stone -block(1, 1) +block(blockid=1, top_index=1) -@material(2, range(11) + [0x10,], solid=True) +@material(blockid=2, data=range(11)+[0x10,], solid=True) def grass(blockid, data): # 0x10 bit means SNOW side_img = terrain_images[3] @@ -650,13 +653,13 @@ def grass(blockid, data): return img # dirt -block(3, 2) +block(blockid=3, top_index=2) # cobblestone -block(4, 16) +block(blockid=4, top_index=16) # wooden plank -block(5, 4) +block(blockid=5, top_index=4) -@material(6, range(16), transparent=True) +@material(blockid=6, data=range(16), transparent=True) def saplings(blockid, data): # usual saplings tex = terrain_images[15] From da142dfb7a720f73b652701f616d6680ba884c79 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Nov 2011 18:18:29 -0400 Subject: [PATCH 17/30] textures re-implemented for blocks up to id 25 --- overviewer_core/textures.py | 140 +++++++++++++++++++++++++++++++++++- 1 file changed, 138 insertions(+), 2 deletions(-) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 0609789..004cf42 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -415,6 +415,8 @@ def tintTexture(im, c): def generate_texture_tuple(img): """ This takes an image and returns the needed tuple for the blockmap dictionary.""" + if img is None: + return None return (img, generate_opaque_mask(img)) ## @@ -547,11 +549,11 @@ def material(blockid=[], data=[0], **kwargs): # make sure blockid and data are iterable try: - blockid = iter(blockid) + iter(blockid) except: blockid = [blockid,] try: - data = iter(data) + iter(data) except: data = [data,] @@ -670,3 +672,137 @@ def saplings(blockid, data): tex = terrain_images[79] return build_sprite(tex) + +# bedrock +block(blockid=7, top_index=17) + +@material(blockid=8, data=range(16), fluid=True, transparent=True) +def water(blockid, data): + watertex = _load_image("water.png") + return build_block(watertex, watertex) + +# other water, glass, and ice (no inner surfaces) +# uses pseudo-ancildata found in iterate.c +@material(blockid=[9, 20, 79], data=range(32), transparent=True, nospawn=True) +def no_inner_surfaces(blockid, data): + if blockid == 9: + texture = _load_image("water.png") + elif blockid == 20: + texture = terrain_images[49] + else: + texture = terrain_images[67] + + if (data & 0b10000) == 16: + top = texture + else: + top = None + + if (data & 0b0001) == 1: + side1 = texture # top left + else: + side1 = None + + if (data & 0b1000) == 8: + side2 = texture # top right + else: + side2 = None + + if (data & 0b0010) == 2: + side3 = texture # bottom left + else: + side3 = None + + if (data & 0b0100) == 4: + side4 = texture # bottom right + else: + side4 = None + + # if nothing shown do not draw at all + if top is None and side3 is None and side4 is None: + return None + + img = build_full_block(top,None,None,side3,side4) + return img + +@material(blockid=[10, 11], data=range(16), fluid=True, transparent=False) +def lava(blockid, data): + lavatex = _load_image("lava.png") + return build_block(lavatex, lavatex) + +# sand +block(blockid=12, top_index=18) +# gravel +block(blockid=13, top_index=19) +# gold ore +block(blockid=14, top_index=32) +# iron ore +block(blockid=15, top_index=33) +# coal ore +block(blockid=16, top_index=34) + +@material(blockid=17, data=range(3), solid=True) +def wood(blockid, data): + top = terrain_images[21] + if data == 0: # normal + return build_block(top, terrain_images[20]) + if data == 1: # birch + return build_block(top, terrain_images[116]) + if data == 2: # pine + return build_block(top, terrain_images[117]) + +@material(blockid=18, data=range(16), transparent=True, solid=True) +def leaves(blockid, data): + t = terrain_images[52] + if data == 1: + # pine! + t = terrain_images[132] + return build_block(t, t) + +# sponge +block(blockid=19, top_index=48) +# lapis lazuli ore +block(blockid=21, top_index=160) +# lapis lazuli block +block(blockid=22, top_index=144) + +# dispensers, furnaces, and burning furnaces +@material(blockid=[23, 61, 62], data=range(6), solid=True) +def furnaces(blockid, data, north): + # first, do the north rotation if needed + 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 + + top = terrain_images[62] + side = terrain_images[45] + + if blockid == 61: + front = terrain_images[44] + elif blockid == 62: + front = terrain_images[61] + elif blockid == 23: + front = terrain_images[46] + + if data == 3: # pointing west + return build_full_block(top, None, None, side, front) + elif data == 4: # pointing north + return build_full_block(top, None, None, front, side) + else: # in any other direction the front can't be seen + return build_full_block(top, None, None, side, side) + +# sandstone +block(blockid=24, top_index=176, side_index=192) +# note block +block(blockid=25, top_index=74) From c9fc7eba622824b7d02bdf482fe7fd38ac06da9d Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Nov 2011 21:10:11 -0400 Subject: [PATCH 18/30] textures added back for blocks up to id 50 --- overviewer_core/textures.py | 549 +++++++++++++++++++++++++++++++++++- 1 file changed, 546 insertions(+), 3 deletions(-) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 004cf42..3b1396c 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -398,6 +398,15 @@ def build_sprite(side): composite.alpha_over(img, otherside, (6,3), otherside) return img +def build_billboard(tex): + """From a texture, create a billboard-like texture such as those used for + tall grass or melon stems.""" + img = Image.new("RGBA", (24,24), bgcolor) + + front = tex.resize((14, 11), Image.ANTIALIAS) + composite.alpha_over(img, front, (5,9)) + return img + def generate_opaque_mask(img): """ Takes the alpha channel of the image and generates a mask (used for lighting the block) that deprecates values of alpha @@ -571,8 +580,12 @@ def material(blockid=[], data=[0], **kwargs): for block in blockid: # set the property sets appropriately for prop in properties: - if kwargs.get(prop, False): - properties[prop].update([block]) + try: + if block in kwargs.get(prop, []): + properties[prop].update([block]) + except TypeError: + if kwargs.get(prop, False): + properties[prop].update([block]) # populate blockmap_generators with our function for d in data: @@ -597,6 +610,32 @@ def block(blockid=[], top_index=None, side_index=None, **kwargs): return build_block(terrain_images[top_index], terrain_images[side_index]) return inner_block +# shortcut function for sprite blocks, defaults to transparent +def sprite(blockid=[], index=None, **kwargs): + new_kwargs = {'transparent' : True} + new_kwargs.update(kwargs) + + if index is None: + raise ValueError("index was not provided") + + @material(blockid=blockid, **new_kwargs) + def inner_sprite(unused_id, unused_data): + return build_sprite(terrain_images[index]) + return inner_sprite + +# shortcut function for billboard blocks, defaults to transparent +def billboard(blockid=[], index=None, **kwargs): + new_kwargs = {'transparent' : True} + new_kwargs.update(kwargs) + + if index is None: + raise ValueError("index was not provided") + + @material(blockid=blockid, **new_kwargs) + def inner_billboard(unused_id, unused_data): + return build_billboard(terrain_images[index]) + return inner_billboard + def generate(path=None,texture_size=24,bgc = (26,26,26,0),north_direction='lower-left'): global _find_file_local_path global bgcolor @@ -683,7 +722,7 @@ def water(blockid, data): # other water, glass, and ice (no inner surfaces) # uses pseudo-ancildata found in iterate.c -@material(blockid=[9, 20, 79], data=range(32), transparent=True, nospawn=True) +@material(blockid=[9, 20, 79], data=range(32), fluid=(9,), transparent=True, nospawn=True) def no_inner_surfaces(blockid, data): if blockid == 9: texture = _load_image("water.png") @@ -806,3 +845,507 @@ def furnaces(blockid, data, north): block(blockid=24, top_index=176, side_index=192) # note block block(blockid=25, top_index=74) + +@material(blockid=26, data=range(12), transparent=True) +def bed(blockid, data, north): + # first get north rotation done + # 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 + + increment = 8 + 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) + return build_full_block(top, None, None, left_face, right_face) + +# powered, detector, and normal rails +@material(blockid=[27, 28, 66], data=range(14), transparent=True) +def rails(blockid, data, north): + # first, do north rotation + # 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 + + img = Image.new("RGBA", (24,24), bgcolor) + + 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] + + ## use transform_image to scale and shear + if data == 0: + track = transform_image_top(raw_straight) + composite.alpha_over(img, track, (0,12), track) + elif data == 6: + track = transform_image_top(raw_corner) + composite.alpha_over(img, track, (0,12), track) + elif data == 7: + track = transform_image_top(raw_corner.rotate(270)) + composite.alpha_over(img, track, (0,12), track) + elif data == 8: + # flip + track = transform_image_top(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90)) + composite.alpha_over(img, track, (0,12), track) + elif data == 9: + track = transform_image_top(raw_corner.transpose(Image.FLIP_TOP_BOTTOM)) + composite.alpha_over(img, track, (0,12), track) + elif data == 1: + track = transform_image_top(raw_straight.rotate(90)) + composite.alpha_over(img, track, (0,12), track) + + #slopes + elif data == 2: # slope going up in +x direction + track = transform_image_slope(raw_straight) + track = track.transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, track, (2,0), track) + # the 2 pixels move is needed to fit with the adjacent tracks + + 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. + 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! + elif data == 4: # slope going up in -y direction + track = transform_image_slope(raw_straight) + composite.alpha_over(img, track, (0,0), track) + + elif data == 5: # slope going up in +y direction + # same as "data == 3" + ImageDraw.Draw(img).line([(1,17),(12,11)],fill=(164,164,164)) + + return img + +# sticky and normal piston body +@material(blockid=[29, 33], data=[0,1,2,3,4,5,8,9,10,11,12,13], transparent=True, solid=True) +def piston(blockid, data, north): + # first, north rotation + # 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 == 29: # sticky + piston_t = terrain_images[106].copy() + else: # normal + piston_t = terrain_images[107].copy() + + # other textures + side_t = terrain_images[108].copy() + back_t = terrain_images[109].copy() + interior_t = terrain_images[110].copy() + + if data & 0x08 == 0x08: # pushed out, non full blocks, tricky stuff + # remove piston texture from piston body + ImageDraw.Draw(side_t).rectangle((0, 0,16,3),outline=(0,0,0,0),fill=(0,0,0,0)) + + if data & 0x07 == 0x0: # down + side_t = side_t.rotate(180) + img = build_full_block(back_t ,None ,None ,side_t, side_t) + + elif data & 0x07 == 0x1: # up + img = build_full_block((interior_t, 4) ,None ,None ,side_t, side_t) + + elif data & 0x07 == 0x2: # east + img = build_full_block(side_t , None, None ,side_t.rotate(90), back_t) + + elif data & 0x07 == 0x3: # west + img = build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), None) + temp = transform_image_side(interior_t) + temp = temp.transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, temp, (9,5), temp) + + elif data & 0x07 == 0x4: # north + img = build_full_block(side_t.rotate(90) ,None ,None , None, side_t.rotate(270)) + temp = transform_image_side(interior_t) + composite.alpha_over(img, temp, (3,5), temp) + + elif data & 0x07 == 0x5: # south + img = build_full_block(side_t.rotate(270) ,None , None ,back_t, side_t.rotate(90)) + + else: # pushed in, normal full blocks, easy stuff + if data & 0x07 == 0x0: # down + side_t = side_t.rotate(180) + img = build_full_block(back_t ,None ,None ,side_t, side_t) + elif data & 0x07 == 0x1: # up + img = build_full_block(piston_t ,None ,None ,side_t, side_t) + elif data & 0x07 == 0x2: # east + img = build_full_block(side_t ,None ,None ,side_t.rotate(90), back_t) + elif data & 0x07 == 0x3: # west + img = build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), piston_t) + elif data & 0x07 == 0x4: # north + img = build_full_block(side_t.rotate(90) ,None ,None ,piston_t, side_t.rotate(270)) + elif data & 0x07 == 0x5: # south + img = build_full_block(side_t.rotate(270) ,None ,None ,back_t, side_t.rotate(90)) + + return img + +# sticky and normal piston shaft +@material(blockid=34, data=[0,1,2,3,4,5,8,9,10,11,12,13], transparent=True) +def piston_extension(blockid, data, north): + # first, north rotation + # 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 (data & 0x8) == 0x8: # sticky + piston_t = terrain_images[106].copy() + else: # normal + piston_t = terrain_images[107].copy() + + # other textures + side_t = terrain_images[108].copy() + back_t = terrain_images[107].copy() + # crop piston body + ImageDraw.Draw(side_t).rectangle((0, 4,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) + + # generate the horizontal piston extension stick + h_stick = Image.new("RGBA", (24,24), bgcolor) + temp = transform_image_side(side_t) + composite.alpha_over(h_stick, temp, (1,7), temp) + temp = transform_image_top(side_t.rotate(90)) + composite.alpha_over(h_stick, temp, (1,1), temp) + # Darken it + sidealpha = h_stick.split()[3] + h_stick = ImageEnhance.Brightness(h_stick).enhance(0.85) + h_stick.putalpha(sidealpha) + + # generate the vertical piston extension stick + v_stick = Image.new("RGBA", (24,24), bgcolor) + temp = transform_image_side(side_t.rotate(90)) + composite.alpha_over(v_stick, temp, (12,6), temp) + temp = temp.transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(v_stick, temp, (1,6), temp) + # Darken it + sidealpha = v_stick.split()[3] + v_stick = ImageEnhance.Brightness(v_stick).enhance(0.85) + v_stick.putalpha(sidealpha) + + # Piston orientation is stored in the 3 first bits + if data & 0x07 == 0x0: # down + side_t = side_t.rotate(180) + img = build_full_block((back_t, 12) ,None ,None ,side_t, side_t) + composite.alpha_over(img, v_stick, (0,-3), v_stick) + elif data & 0x07 == 0x1: # up + img = Image.new("RGBA", (24,24), bgcolor) + img2 = build_full_block(piston_t ,None ,None ,side_t, side_t) + composite.alpha_over(img, v_stick, (0,4), v_stick) + composite.alpha_over(img, img2, (0,0), img2) + elif data & 0x07 == 0x2: # east + img = build_full_block(side_t ,None ,None ,side_t.rotate(90), None) + temp = transform_image_side(back_t).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, temp, (2,2), temp) + composite.alpha_over(img, h_stick, (6,3), h_stick) + elif data & 0x07 == 0x3: # west + img = Image.new("RGBA", (24,24), bgcolor) + img2 = build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), piston_t) + composite.alpha_over(img, h_stick, (0,0), h_stick) + composite.alpha_over(img, img2, (0,0), img2) + elif data & 0x07 == 0x4: # north + img = build_full_block(side_t.rotate(90) ,None ,None , piston_t, side_t.rotate(270)) + composite.alpha_over(img, h_stick.transpose(Image.FLIP_LEFT_RIGHT), (0,0), h_stick.transpose(Image.FLIP_LEFT_RIGHT)) + elif data & 0x07 == 0x5: # south + img = Image.new("RGBA", (24,24), bgcolor) + img2 = build_full_block(side_t.rotate(270) ,None ,None ,None, side_t.rotate(90)) + temp = transform_image_side(back_t) + composite.alpha_over(img2, temp, (10,2), temp) + composite.alpha_over(img, img2, (0,0), img2) + composite.alpha_over(img, h_stick.transpose(Image.FLIP_LEFT_RIGHT), (-3,2), h_stick.transpose(Image.FLIP_LEFT_RIGHT)) + + return img + +# cobweb +sprite(blockid=30, index=11) + +@material(blockid=31, data=range(3), transparent=True) +def tall_grass(blockid, data): + if data == 0: # dead shrub + texture = terrain_images[55] + elif data == 1: # tall grass + texture = terrain_images[39] + elif data == 2: # fern + texture = terrain_images[56] + + return build_billboard(texture) + +# dead bush +billboard(blockid=32, index=55) + +@material(blockid=35, data=range(16), solid=True) +def wool(blockid, data): + if data == 0: # white + texture = terrain_images[64] + elif data == 1: # orange + texture = terrain_images[210] + elif data == 2: # magenta + texture = terrain_images[194] + elif data == 3: # light blue + texture = terrain_images[178] + elif data == 4: # yellow + texture = terrain_images[162] + elif data == 5: # light green + texture = terrain_images[146] + elif data == 6: # pink + texture = terrain_images[130] + elif data == 7: # grey + texture = terrain_images[114] + elif data == 8: # light grey + texture = terrain_images[225] + elif data == 9: # cyan + texture = terrain_images[209] + elif data == 10: # purple + texture = terrain_images[193] + elif data == 11: # blue + texture = terrain_images[177] + elif data == 12: # brown + texture = terrain_images[161] + elif data == 13: # dark green + texture = terrain_images[145] + elif data == 14: # red + texture = terrain_images[129] + elif data == 15: # black + texture = terrain_images[113] + + return build_block(texture, texture) + +# dandelion +sprite(blockid=37, index=13) +# rose +sprite(blockid=38, index=12) +# brown mushroom +sprite(blockid=39, index=29) +# red mushroom +sprite(blockid=40, index=28) +# block of gold +block(blockid=41, top_index=23) +# block of iron +block(blockid=42, top_index=22) + +# double slabs and slabs +@material(blockid=[43, 44], data=range(6), transparent=(44,), solid=(43,)) +def slabs(blockid, data): + if data == 0: # stone slab + top = terrain_images[6] + side = terrain_images[5] + elif data == 1: # stone slab + top = terrain_images[176] + side = terrain_images[192] + elif data == 2: # wooden slab + top = side = terrain_images[4] + elif data == 3: # cobblestone slab + top = side = terrain_images[16] + elif data == 4: # brick? + top = side = terrain_images[7] + elif data == 5: # stone brick? + top = side = terrain_images[54] + + if blockid == 43: # double slab + return build_block(top, side) + + # plain slab + top = transform_image_top(top) + side = transform_image_side(side) + otherside = side.transpose(Image.FLIP_LEFT_RIGHT) + + sidealpha = side.split()[3] + side = ImageEnhance.Brightness(side).enhance(0.9) + side.putalpha(sidealpha) + othersidealpha = otherside.split()[3] + otherside = ImageEnhance.Brightness(otherside).enhance(0.8) + otherside.putalpha(othersidealpha) + + img = Image.new("RGBA", (24,24), bgcolor) + composite.alpha_over(img, side, (0,12), side) + composite.alpha_over(img, otherside, (12,12), otherside) + composite.alpha_over(img, top, (0,6), top) + + return img + +# brick block +block(blockid=45, top_index=7) +# TNT +block(blockid=46, top_index=9, side_index=8) +# bookshelf +block(blockid=47, top_index=4, side_index=35) +# moss stone +block(blockid=48, top_index=36) +# obsidian +block(blockid=49, top_index=37) + +# torch, redstone torch (off), redstone torch(on) +@material(blockid=[50, 75, 76], data=[1, 2, 3, 4, 5], transparent=True) +def torches(blockid, data, north): + # first, north rotations + 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 + + # 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), bgcolor) + 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) + + elif data == 2: # pointing north + torch = torch.rotate(rotation, Image.NEAREST) + img = build_full_block(None, None, torch, None, None, None) + + elif data == 3: # pointing west + torch = torch.rotate(rotation, Image.NEAREST) + img = build_full_block(None, torch, None, None, None, None) + + elif data == 4: # pointing east + torch = torch.rotate(-rotation, Image.NEAREST) + img = build_full_block(None, None, None, None, torch, None) + + elif data == 5: # standing on the floor + # compose a "3d torch". + img = Image.new("RGBA", (24,24), bgcolor) + + 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, (7,5)) + composite.alpha_over(img, small_crop, (6,6)) + composite.alpha_over(img, small_crop, (7,6)) + composite.alpha_over(img, slice, (7,7)) + + return img From 2cc160bcaa9e769df9f3426d19108685e5c24235 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Thu, 3 Nov 2011 13:52:02 +0100 Subject: [PATCH 19/30] Add up to blockid 85. --- overviewer_core/textures.py | 619 ++++++++++++++++++++++++++++++++++++ 1 file changed, 619 insertions(+) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 3b1396c..9a9794d 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -1349,3 +1349,622 @@ def torches(blockid, data, north): composite.alpha_over(img, slice, (7,7)) return img + +# fire +@material(blockid=51, data=range(16), transparent=True) +def fire(blockid, data): + 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), bgcolor) + + 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 + +# monster spawner +block(blockid=52, top_index=34) + +# wooden, cobblestone, red brick, stone brick and netherbrick stairs. +@material(blockid=[53,67,108,109,114], data=range(4), transparent=True) +def stairs(blockid, data, north): + + # first, north rotations + 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 == 53: # wooden + texture = terrain_images[4] + elif blockid == 67: # cobblestone + texture = terrain_images[16] + elif blockid == 108: # red brick stairs + texture = terrain_images[7] + elif blockid == 109: # stone brick stairs + texture = terrain_images[54] + elif blockid == 114: # netherbrick stairs + texture = terrain_images[224] + + side = texture.copy() + half_block_u = texture.copy() # up, down, left, right + half_block_d = texture.copy() + half_block_l = texture.copy() + half_block_r = texture.copy() + + # generate needed geometries + ImageDraw.Draw(side).rectangle((0,0,7,6),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(half_block_u).rectangle((0,8,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(half_block_d).rectangle((0,0,15,6),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(half_block_l).rectangle((8,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(half_block_r).rectangle((0,0,7,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + if data == 0: # ascending south + img = build_full_block(half_block_r, None, None, half_block_d, side.transpose(Image.FLIP_LEFT_RIGHT)) + tmp1 = transform_image_side(half_block_u) + + # Darken the vertical part of the second step + sidealpha = tmp1.split()[3] + # darken it a bit more than usual, looks better + tmp1 = ImageEnhance.Brightness(tmp1).enhance(0.8) + tmp1.putalpha(sidealpha) + + composite.alpha_over(img, tmp1, (6,3)) + tmp2 = transform_image_top(half_block_l) + composite.alpha_over(img, tmp2, (0,6)) + + elif data == 1: # ascending north + img = Image.new("RGBA", (24,24), bgcolor) # first paste the texture in the back + tmp1 = transform_image_top(half_block_r) + composite.alpha_over(img, tmp1, (0,6)) + tmp2 = build_full_block(half_block_l, None, None, texture, side) + composite.alpha_over(img, tmp2) + + elif data == 2: # ascending west + img = Image.new("RGBA", (24,24), bgcolor) # first paste the texture in the back + tmp1 = transform_image_top(half_block_u) + composite.alpha_over(img, tmp1, (0,6)) + tmp2 = build_full_block(half_block_d, None, None, side, texture) + composite.alpha_over(img, tmp2) + + elif data == 3: # ascending east + img = build_full_block(half_block_u, None, None, side.transpose(Image.FLIP_LEFT_RIGHT), half_block_d) + tmp1 = transform_image_side(half_block_u).transpose(Image.FLIP_LEFT_RIGHT) + + # Darken the vertical part of the second step + sidealpha = tmp1.split()[3] + # darken it a bit more than usual, looks better + tmp1 = ImageEnhance.Brightness(tmp1).enhance(0.7) + tmp1.putalpha(sidealpha) + + composite.alpha_over(img, tmp1, (6,3)) + tmp2 = transform_image_top(half_block_d) + composite.alpha_over(img, tmp2, (0,6)) + + # touch up a (horrible) pixel + img.putpixel((18,3),(0,0,0,0)) + + return img + +# normal and locked chest (locked was the one used in april fools' day) +# uses pseudo-ancildata found in iterate.c +@material(blockid=[54,95], data=range(12), transparent=True) +def chests(blockid, data): + # First two bits of the pseudo data store if it's a single chest + # or it's a double chest, first half or second half (left to right). + # The last two bits store the orientation. + + # No need for north stuff, uses pseudo data and rotates with the map + + 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 + +# redstone wire +# uses pseudo-ancildata found in iterate.c +@material(blockid=55, data=range(128), transparent=True) +def wire(blockid, data): + + 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), bgcolor) + 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 + +# crafting table +# needs two different sides +@material(blockid=58, solid=True) +def crafting_table(blockid, data): + 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) + return img + +# crops +@material(blockid=59, data=range(8), transparent=True) +def crops(blockid, data): + raw_crop = terrain_images[88+data] + crop1 = transform_image_top(raw_crop) + crop2 = transform_image_side(raw_crop) + crop3 = crop2.transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), bgcolor) + 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 + +# signposts +@material(blockid=63, data=range(16), transparent=True) +def signpost(blockid, data, north): + + # first north rotations + if north == 'upper-left': + data = (data + 4) % 16 + elif north == 'upper-right': + data = (data + 8) % 16 + elif north == 'lower-right': + data = (data + 12) % 16 + + texture = terrain_images[4].copy() + # cut the planks to the size of a signpost + ImageDraw.Draw(texture).rectangle((0,12,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + # If the signpost is looking directly to the image, draw some + # random dots, they will look as text. + if data in (0,1,2,3,4,5,15): + for i in range(15): + x = randint(4,11) + y = randint(3,7) + texture.putpixel((x,y),(0,0,0,255)) + + # Minecraft uses wood texture for the signpost stick + texture_stick = terrain_images[20] + texture_stick = texture_stick.resize((12,12), Image.ANTIALIAS) + ImageDraw.Draw(texture_stick).rectangle((2,0,12,12),outline=(0,0,0,0),fill=(0,0,0,0)) + + img = Image.new("RGBA", (24,24), bgcolor) + + # W N ~90 E S ~270 + angles = (330.,345.,0.,15.,30.,55.,95.,120.,150.,165.,180.,195.,210.,230.,265.,310.) + angle = math.radians(angles[data]) + post = transform_image_angle(texture, angle) + + # choose the position of the "3D effect" + incrementx = 0 + if data in (1,6,7,8,9,14): + incrementx = -1 + elif data in (3,4,5,11,12,13): + incrementx = +1 + + composite.alpha_over(img, texture_stick,(11, 8),texture_stick) + # post2 is a brighter signpost pasted with a small shift, + # gives to the signpost some 3D effect. + post2 = ImageEnhance.Brightness(post).enhance(1.2) + composite.alpha_over(img, post2,(incrementx, -3),post2) + composite.alpha_over(img, post, (0,-2), post) + + return img + + +# wooden and iron door +@material(blockid=[64,71], data=range(16), transparent=True) +def door(blockid, data, north): + #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 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), bgcolor) + if (data & 0x03) == 0: # northeast corner + 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: # southeast corner + 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: # southwest corner + 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: # northwest corner + 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 + +# ladder +@material(blockd=65, data=[2, 3, 4, 5], transparent = True) +def ladder(blockid, data, north): + + # first north rotations + 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 + + img = Image.new("RGBA", (24,24), bgcolor) + raw_texture = terrain_images[83] + + 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) + composite.alpha_over(img, tex, (0,6), tex) + return generate_texture_tuple(img, blockID) + if data == 2: + tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (12,6), tex) + return generate_texture_tuple(img, blockID) + if data == 3: + tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (0,0), tex) + return generate_texture_tuple(img, blockID) + if data == 4: + tex = transform_image_side(raw_texture) + composite.alpha_over(img, tex, (12,0), tex) + return generate_texture_tuple(img, blockID) + + +# wall signs +@material(blockid=68, data=[2, 3, 4, 5], trasnparent=True) +def wall_sign(blockid, data, north): # wall sign + + # first north rotations + 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 + + texture = terrain_images[4].copy() + # cut the planks to the size of a signpost + ImageDraw.Draw(texture).rectangle((0,12,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + # draw some random black dots, they will look as text + """ don't draw text at the moment, they are used in blank for decoration + + if data in (3,4): + for i in range(15): + x = randint(4,11) + y = randint(3,7) + texture.putpixel((x,y),(0,0,0,255)) + """ + + img = Image.new("RGBA", (24,24), bgcolor) + + incrementx = 0 + if data == 2: # east + incrementx = +1 + sign = build_full_block(None, None, None, None, texture) + elif data == 3: # west + incrementx = -1 + sign = build_full_block(None, texture, None, None, None) + elif data == 4: # north + incrementx = +1 + sign = build_full_block(None, None, texture, None, None) + elif data == 5: # south + incrementx = -1 + sign = build_full_block(None, None, None, texture, None) + + sign2 = ImageEnhance.Brightness(sign).enhance(1.2) + composite.alpha_over(img, sign2,(incrementx, 2),sign2) + composite.alpha_over(img, sign, (0,3), sign) + + return img + + +# wooden and stone pressure plates +@material(blockid=[70, 72], data=[0,1], transparent=True) +def pressure_plate(blockid, data): + if blockid == 70: # stone + t = terrain_images[1].copy() + else: # wooden + t = terrain_images[4].copy() + + # cut out the outside border, pressure plates are smaller + # than a normal block + ImageDraw.Draw(t).rectangle((0,0,15,15),outline=(0,0,0,0)) + + # create the textures and a darker version to make a 3d by + # pasting them with an offstet of 1 pixel + img = Image.new("RGBA", (24,24), bgcolor) + + top = transform_image_top(t) + + alpha = top.split()[3] + topd = ImageEnhance.Brightness(top).enhance(0.8) + topd.putalpha(alpha) + + #show it 3d or 2d if unpressed or pressed + if data == 0: + composite.alpha_over(img,topd, (0,12),topd) + composite.alpha_over(img,top, (0,11),top) + elif data == 1: + composite.alpha_over(img,top, (0,12),top) + + return img + + +# nether and normal fences +# uses pseudo-ancildata found in iterate.c +@material(blockid=[85, 113], data=range(16), transparent=True) +def fence(blockid, data): + # no need for north rotations, it uses pseudo data. + # create needed images for Big stick fence + if blockid == 85: # normal fence + fence_top = terrain_images[4].copy() + fence_side = terrain_images[4].copy() + fence_small_side = terrain_images[4].copy() + else: # netherbrick fence + fence_top = terrain_images[224].copy() + fence_side = terrain_images[224].copy() + fence_small_side = terrain_images[224].copy() + + # generate the textures of the fence + ImageDraw.Draw(fence_top).rectangle((0,0,5,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(fence_top).rectangle((10,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(fence_top).rectangle((0,0,15,5),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(fence_top).rectangle((0,10,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + ImageDraw.Draw(fence_side).rectangle((0,0,5,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(fence_side).rectangle((10,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + # Create the sides and the top of the big stick + fence_side = transform_image_side(fence_side) + fence_other_side = fence_side.transpose(Image.FLIP_LEFT_RIGHT) + fence_top = transform_image_top(fence_top) + + # Darken the sides slightly. These methods also affect the alpha layer, + # so save them first (we don't want to "darken" the alpha layer making + # the block transparent) + sidealpha = fence_side.split()[3] + fence_side = ImageEnhance.Brightness(fence_side).enhance(0.9) + fence_side.putalpha(sidealpha) + othersidealpha = fence_other_side.split()[3] + fence_other_side = ImageEnhance.Brightness(fence_other_side).enhance(0.8) + fence_other_side.putalpha(othersidealpha) + + # Compose the fence big stick + fence_big = Image.new("RGBA", (24,24), bgcolor) + 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,0),fence_top) + + # Now render the small sticks. + # Create needed images + ImageDraw.Draw(fence_small_side).rectangle((0,0,15,0),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(fence_small_side).rectangle((0,4,15,6),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(fence_small_side).rectangle((0,10,15,16),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(fence_small_side).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(fence_small_side).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + # Create the sides and the top of the small sticks + fence_small_side = transform_image_side(fence_small_side) + fence_small_other_side = fence_small_side.transpose(Image.FLIP_LEFT_RIGHT) + + # Darken the sides slightly. These methods also affect the alpha layer, + # so save them first (we don't want to "darken" the alpha layer making + # the block transparent) + sidealpha = fence_small_other_side.split()[3] + fence_small_other_side = ImageEnhance.Brightness(fence_small_other_side).enhance(0.9) + fence_small_other_side.putalpha(sidealpha) + sidealpha = fence_small_side.split()[3] + fence_small_side = ImageEnhance.Brightness(fence_small_side).enhance(0.9) + fence_small_side.putalpha(sidealpha) + + # Create img to compose the fence + img = Image.new("RGBA", (24,24), bgcolor) + + # Position of fence small sticks in img. + # These postitions are strange because the small sticks of the + # fence are at the very left and at the very right of the 16x16 images + pos_top_left = (2,3) + pos_top_right = (10,3) + pos_bottom_right = (10,7) + pos_bottom_left = (2,7) + + # +x axis points top right direction + # +y axis points bottom right direction + # First compose small sticks in the back of the image, + # then big stick and thecn small sticks in the front. + + if (data & 0b0001) == 1: + composite.alpha_over(img,fence_small_side, pos_top_left,fence_small_side) # top left + if (data & 0b1000) == 8: + composite.alpha_over(img,fence_small_other_side, pos_top_right,fence_small_other_side) # top right + + composite.alpha_over(img,fence_big,(0,0),fence_big) + + if (data & 0b0010) == 2: + composite.alpha_over(img,fence_small_other_side, pos_bottom_left,fence_small_other_side) # bottom left + if (data & 0b0100) == 4: + composite.alpha_over(img,fence_small_side, pos_bottom_right,fence_small_side) # bottom right + + return img From eb1f79ddf122d05e245aa978d43aac9280dd06cc Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Thu, 3 Nov 2011 11:44:36 -0300 Subject: [PATCH 20/30] Fixed typo --- overviewer_core/textures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 9a9794d..a7b93e2 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -1783,7 +1783,7 @@ def ladder(blockid, data, north): # wall signs -@material(blockid=68, data=[2, 3, 4, 5], trasnparent=True) +@material(blockid=68, data=[2, 3, 4, 5], transparent=True) def wall_sign(blockid, data, north): # wall sign # first north rotations From acb2d159f5552792ea8b935ea1989bdddd808751 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Thu, 3 Nov 2011 15:56:01 -0400 Subject: [PATCH 21/30] filled in simpler blocks between id 50 and 85 --- overviewer_core/textures.py | 82 +++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index a7b93e2..ceb16c6 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -1589,6 +1589,11 @@ def wire(blockid, data): return img +# diamond ore +block(blockid=56, top_index=50) +# diamond block +block(blockid=57, top_index=24) + # crafting table # needs two different sides @material(blockid=58, solid=True) @@ -1614,6 +1619,14 @@ def crops(blockid, data): composite.alpha_over(img, crop3, (6,3), crop3) return img +# farmland +@material(blockid=60, data=range(9), solid=True) +def farmland(blockid, data): + top = terrain_images[86] + if data == 0: + top = terrain_images[87] + return build_block(top, terrain_images[2]) + # signposts @material(blockid=63, data=range(16), transparent=True) def signpost(blockid, data, north): @@ -1839,6 +1852,9 @@ def wall_sign(blockid, data, north): # wall sign return img +## +## not rendered: levers +## # wooden and stone pressure plates @material(blockid=[70, 72], data=[0,1], transparent=True) @@ -1871,6 +1887,72 @@ def pressure_plate(blockid, data): return img +# normal and glowing redstone ore +block(blockid=[73, 74], top_index=51) + +## +## not rendered: buttons +## + +# snow +@material(blockid=78, data=range(8), transparent=True) +def snow(blockid, data): + # still not rendered correctly: data other than 0 + tex = terrain_images[66] + + img = Image.new("RGBA", (24,24), bgcolor) + + top = transform_image_top(tex) + side = transform_image_side(tex) + otherside = side.transpose(Image.FLIP_LEFT_RIGHT) + + composite.alpha_over(img, side, (0,6), side) + composite.alpha_over(img, otherside, (12,6), otherside) + composite.alpha_over(img, top, (0,9), top) + + return img + +# snow block +block(blockid=80, top_index=66) + +# cactus +@material(blockid=81, data=range(15), transparent=True, solid=True, nospawn=True) +def cactus(blockid, data): + top = terrain_images[69] + side = terrain_images[70] + + img = Image.new("RGBA", (24,24), bgcolor) + + top = transform_image_top(top) + side = transform_image_side(side) + otherside = side.transpose(Image.FLIP_LEFT_RIGHT) + + sidealpha = side.split()[3] + side = ImageEnhance.Brightness(side).enhance(0.9) + side.putalpha(sidealpha) + othersidealpha = otherside.split()[3] + otherside = ImageEnhance.Brightness(otherside).enhance(0.8) + otherside.putalpha(othersidealpha) + + composite.alpha_over(img, side, (1,6), side) + composite.alpha_over(img, otherside, (11,6), otherside) + composite.alpha_over(img, top, (0,0), top) + + return img + +# clay block +block(blockid=82, top_index=72) + +# sugar cane +@material(blockid=83, data=range(16), transparent=True) +def sugar_cane(blockid, data): + tex = terrain_images[73] + return build_sprite(tex) + +# jukebox +@material(blockid=84, data=range(16), solid=True) +def jukebox(blockid, data): + return build_block(terrain_images[75], terrain_images[74]) # nether and normal fences # uses pseudo-ancildata found in iterate.c From c1fdcffad7932be804664414b033b5542326f995 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Thu, 3 Nov 2011 16:07:56 -0400 Subject: [PATCH 22/30] fixed rendering error in snow and half-steps --- overviewer_core/textures.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index ceb16c6..ed0c21a 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -1250,6 +1250,11 @@ def slabs(blockid, data): if blockid == 43: # double slab return build_block(top, side) + # cut the side texture in half + mask = side.crop((0,8,16,16)) + side = Image.new(side.mode, side.size, bgcolor) + composite.alpha_over(side, mask,(0,0,16,8), mask) + # plain slab top = transform_image_top(top) side = transform_image_side(side) @@ -1898,12 +1903,18 @@ block(blockid=[73, 74], top_index=51) @material(blockid=78, data=range(8), transparent=True) def snow(blockid, data): # still not rendered correctly: data other than 0 + tex = terrain_images[66] + # make the side image, top 3/4 transparent + mask = tex.crop((0,12,16,16)) + sidetex = Image.new(tex.mode, tex.size, bgcolor) + composite.alpha_over(sidetex, mask, (0,12,16,16), mask) + img = Image.new("RGBA", (24,24), bgcolor) top = transform_image_top(tex) - side = transform_image_side(tex) + side = transform_image_side(sidetex) otherside = side.transpose(Image.FLIP_LEFT_RIGHT) composite.alpha_over(img, side, (0,6), side) From 1cfe21153ac7ceecbba135ebc66dbe0aee07ff2f Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Mon, 7 Nov 2011 11:56:35 +0100 Subject: [PATCH 23/30] All blocks before 1.9pre added. --- overviewer_core/textures.py | 279 ++++++++++++++++++++++++++++++++++++ 1 file changed, 279 insertions(+) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index ed0c21a..71581f8 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -2061,3 +2061,282 @@ def fence(blockid, data): composite.alpha_over(img,fence_small_side, pos_bottom_right,fence_small_side) # bottom right return img + +# pumpkin +@material(blockid=[86, 91], data=range(4), solid=True) +def pumpkin(blockid, data, north): # pumpkins, jack-o-lantern + # north rotation + 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 + + # texture generation + top = terrain_images[102] + frontID = 119 if blockid == 86 else 120 + 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 + +# netherrack +block(blockid=87, top_index=103) + +# soul sand +block(blockid=88, top_index=104) + +# glowstone +block(blockid=89, top_index=105) + +# portal +@material(blockid=90, data=[1, 2, 4, 8], transparent=True) +def portal(blockid, data): + # no north orientation uses pseudo data + portaltexture = _load_image("portal.png") + img = Image.new("RGBA", (24,24), bgcolor) + + 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 + +# cake! +# TODO is rendered un-bitten +@material(blockid=92, data=range(6), transparent=True) +def cake(blockid, data): + + # choose textures for cake + top = terrain_images[121] + side = terrain_images[122] + top = transform_image_top(top) + side = transform_image_side(side) + otherside = side.transpose(Image.FLIP_LEFT_RIGHT) + + # darken sides slightly + sidealpha = side.split()[3] + side = ImageEnhance.Brightness(side).enhance(0.9) + side.putalpha(sidealpha) + othersidealpha = otherside.split()[3] + otherside = ImageEnhance.Brightness(otherside).enhance(0.8) + otherside.putalpha(othersidealpha) + + img = Image.new("RGBA", (24,24), bgcolor) + + # composite the cake + composite.alpha_over(img, side, (1,6), side) + composite.alpha_over(img, otherside, (11,7), otherside) # workaround, fixes a hole + composite.alpha_over(img, otherside, (12,6), otherside) + composite.alpha_over(img, top, (0,6), top) + + return img + +# redstone repeaters ON and OFF +@material(blockid=[93,94], data=range(16), transparent=True) +def repeater(blockid, data, north): + # north rotation + # 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 + + # generate the diode + top = terrain_images[131] if blockid == 93 else terrain_images[147] + side = terrain_images[5] + increment = 13 + + 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) + + # compose a "3d" redstone torch + t = terrain_images[115].copy() if blockid == 93 else terrain_images[99].copy() + torch = Image.new("RGBA", (24,24), bgcolor) + + t_crop = t.crop((2,2,14,14)) + slice = t_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(torch, slice, (6,4)) + composite.alpha_over(torch, t_crop, (5,5)) + composite.alpha_over(torch, t_crop, (6,5)) + composite.alpha_over(torch, slice, (6,6)) + + # paste redstone torches everywhere! + # 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 = (0,2) + static_torch = (5,-1) + + elif (data & 0xC) == 8: # three ticks delay + moving_torch = (-1,2) + static_torch = (5,-1) + + elif (data & 0xC) == 12: # four ticks delay + moving_torch = (-2,3) + 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) + + return img + +# trapdoor +# TODO the trapdoor is looks like a sprite when opened, that's not good +@material(blockid=96, data=range(8), transparent=True) +def trapdoor(blockid, data, north): + + # north rotation + # 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 + + # texture generation + texture = terrain_images[84] + if data & 0x4 == 0x4: # opened trapdoor + if data & 0x3 == 0: # west + img = build_full_block(None, None, None, None, texture) + if data & 0x3 == 1: # east + img = build_full_block(None, texture, None, None, None) + if data & 0x3 == 2: # south + img = build_full_block(None, None, texture, None, None) + if data & 0x3 == 3: # north + img = build_full_block(None, None, None, texture, None) + + elif data & 0x4 == 0: # closed trapdoor + img = build_full_block((texture, 12), None, None, texture, texture) + + return img From 3f628dcc1b159e04ef9613de7a921f6018a4688a Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Mon, 7 Nov 2011 13:16:35 +0100 Subject: [PATCH 24/30] Add all the 1.9 pre-release blocks. --- overviewer_core/textures.py | 451 ++++++++++++++++++++++++++++++++++++ 1 file changed, 451 insertions(+) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 71581f8..77a00d0 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -2340,3 +2340,454 @@ def trapdoor(blockid, data, north): img = build_full_block((texture, 12), None, None, texture, texture) return img + +# block with hidden silverfish (stone, cobblestone and stone brick) +@material(blockid=97, data=range(3), solid=True) +def hidden_silverfish(blockid, data): + if data == 0: # stone + t = terrain_images[1] + elif data == 1: # cobblestone + t = terrain_images[16] + elif data == 2: # stone brick + t = terrain_images[54] + + img = build_block(t, t) + + return img + +# stone brick +@material(blockid=98, data=range(3), solid=True) +def stone_brick(blockid, data): + if data == 0: # normal + t = terrain_images[54] + elif data == 1: # mossy + t = terrain_images[100] + else: # cracked + t = terrain_images[101] + + img = build_full_block(t, None, None, t, t) + + return img + +# huge brown and red mushroom +@material(blockid=[99,100], data=range(11), solid=True) +def huge_mushroom(blockid, data, north): + # north rotation + if north == 'upper-left': + if data == 1: data = 3 + elif data == 2: data = 6 + elif data == 3: data = 9 + elif data == 4: data = 2 + elif data == 6: data = 8 + elif data == 7: data = 1 + elif data == 8: data = 4 + elif data == 9: data = 7 + elif north == 'upper-right': + if data == 1: data = 9 + elif data == 2: data = 8 + elif data == 3: data = 7 + elif data == 4: data = 6 + elif data == 6: data = 4 + elif data == 7: data = 3 + elif data == 8: data = 2 + elif data == 9: data = 1 + elif north == 'lower-right': + if data == 1: data = 7 + elif data == 2: data = 4 + elif data == 3: data = 1 + elif data == 4: data = 2 + elif data == 6: data = 8 + elif data == 7: data = 9 + elif data == 8: data = 6 + elif data == 9: data = 3 + + # texture generation + if blockid == 99: # brown + cap = terrain_images[126] + else: # red + cap = terrain_images[125] + + stem = terrain_images[141] + porous = terrain_images[142] + + if data == 0: # fleshy piece + img = build_full_block(porous, None, None, porous, porous) + + if data == 1: # north-east corner + img = build_full_block(cap, None, None, cap, porous) + + if data == 2: # east side + img = build_full_block(cap, None, None, porous, porous) + + if data == 3: # south-east corner + img = build_full_block(cap, None, None, porous, cap) + + if data == 4: # north side + img = build_full_block(cap, None, None, cap, porous) + + if data == 5: # top piece + img = build_full_block(cap, None, None, porous, porous) + + if data == 6: # south side + img = build_full_block(cap, None, None, cap, porous) + + if data == 7: # north-west corner + img = build_full_block(cap, None, None, cap, cap) + + if data == 8: # west side + img = build_full_block(cap, None, None, porous, cap) + + if data == 9: # south-west corner + img = build_full_block(cap, None, None, porous, cap) + + if data == 10: # stem + img = build_full_block(porous, None, None, stem, stem) + + return img + +# iron bars and glass pane +# TODO glass pane is not a sprite, it has a texture for the side, +# at the moment is not used +@material(blockid=[101,102], data=range(16), transparent=True) +def panes(blockid, data): + # no north rotation, uses pseudo data + if blockid == 101: + # iron bars + t = terrain_images[85] + else: + # glass panes + t = terrain_images[49] + left = t.copy() + right = t.copy() + + # generate the four small pieces of the glass pane + ImageDraw.Draw(right).rectangle((0,0,7,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(left).rectangle((8,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + up_left = transform_image_side(left) + up_right = transform_image_side(right).transpose(Image.FLIP_TOP_BOTTOM) + dw_right = transform_image_side(right) + dw_left = transform_image_side(left).transpose(Image.FLIP_TOP_BOTTOM) + + # Create img to compose the texture + img = Image.new("RGBA", (24,24), bgcolor) + + # +x axis points top right direction + # +y axis points bottom right direction + # First compose things in the back of the image, + # then things in the front. + + if (data & 0b0001) == 1 or data == 0: + composite.alpha_over(img,up_left, (6,3),up_left) # top left + if (data & 0b1000) == 8 or data == 0: + composite.alpha_over(img,up_right, (6,3),up_right) # top right + if (data & 0b0010) == 2 or data == 0: + composite.alpha_over(img,dw_left, (6,3),dw_left) # bottom left + if (data & 0b0100) == 4 or data == 0: + composite.alpha_over(img,dw_right, (6,3),dw_right) # bottom right + + return img + +# melon +block(blockid=103, top_index=137, side_index=136, solid=True) + +# pumpkin and melon stem +# TODO To render it as in game needs from pseudo data and ancil data: +# once fully grown the stem bends to the melon/pumpkin block, +# at the moment only render the growing stem +@material(blockid=[104,105], data=range(8), transparent=True) +def stem(blockid, data, north): + # the ancildata value indicates how much of the texture + # is shown. + if data & 7 == 0: + # not fully grown stem or no pumpkin/melon touching it, + # straight up stem + t = terrain_images[111].copy() + img = Image.new("RGBA", (16,16), bgcolor) + composite.alpha_over(img, t, (0, int(16 - 16*((data + 1)/8.))), t) + img = build_sprite(t) + if data & 7 == 7: + # fully grown stem gets brown color! + # there is a conditional in rendermode-normal to not + # tint the data value 7 + img = tintTexture(img, (211,169,116)) + return img + + else: # fully grown, and a pumpking/melon touching it, + # corner stem + return None + +# vines +# TODO multiple sides of a block can contain vines! At the moment +# only pure directions are rendered +# (source http://www.minecraftwiki.net/wiki/Data_values#Vines) +@material(blockid=106, data=range(8), transparent=True) +def vines(blockid, data, north): + # north rotation + if north == 'upper-left': + if data == 1: data = 2 + elif data == 4: data = 8 + elif data == 8: data = 1 + elif data == 2: data = 4 + elif north == 'upper-right': + if data == 1: data = 4 + elif data == 4: data = 1 + elif data == 8: data = 2 + elif data == 2: data = 8 + elif north == 'lower-right': + if data == 1: data = 8 + elif data == 4: data = 2 + elif data == 8: data = 4 + elif data == 2: data = 1 + + # texture generation + img = Image.new("RGBA", (24,24), bgcolor) + raw_texture = terrain_images[143] + + if data == 2: # south + tex = transform_image_side(raw_texture) + composite.alpha_over(img, tex, (0,6), tex) + + if data == 1: # east + tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (12,6), tex) + + if data == 4: # west + tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (0,0), tex) + + if data == 8: # north + tex = transform_image_side(raw_texture) + composite.alpha_over(img, tex, (12,0), tex) + + return img + +# fence gates +@material(blockid=107, data=range(8), transparent=True) +def fence_gate(blockid, data, north): + + # north rotation + opened = False + if data & 0x4: + data = data & 0x3 + opened = True + 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 opened: + data = data | 0x4 + + # create the closed gate side + gate_side = terrain_images[4].copy() + gate_side_draw = ImageDraw.Draw(gate_side) + gate_side_draw.rectangle((7,0,15,0),outline=(0,0,0,0),fill=(0,0,0,0)) + gate_side_draw.rectangle((7,4,9,6),outline=(0,0,0,0),fill=(0,0,0,0)) + gate_side_draw.rectangle((7,10,15,16),outline=(0,0,0,0),fill=(0,0,0,0)) + gate_side_draw.rectangle((0,12,15,16),outline=(0,0,0,0),fill=(0,0,0,0)) + gate_side_draw.rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + gate_side_draw.rectangle((14,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + # darken the sides slightly, as with the fences + sidealpha = gate_side.split()[3] + gate_side = ImageEnhance.Brightness(gate_side).enhance(0.9) + gate_side.putalpha(sidealpha) + + # create the other sides + mirror_gate_side = transform_image_side(gate_side.transpose(Image.FLIP_LEFT_RIGHT)) + gate_side = transform_image_side(gate_side) + gate_other_side = gate_side.transpose(Image.FLIP_LEFT_RIGHT) + mirror_gate_other_side = mirror_gate_side.transpose(Image.FLIP_LEFT_RIGHT) + + # Create img to compose the fence gate + img = Image.new("RGBA", (24,24), bgcolor) + + if data & 0x4: + # opened + data = data & 0x3 + if data == 0: + composite.alpha_over(img, gate_side, (2,8), gate_side) + composite.alpha_over(img, gate_side, (13,3), gate_side) + elif data == 1: + composite.alpha_over(img, gate_other_side, (-1,3), gate_other_side) + composite.alpha_over(img, gate_other_side, (10,8), gate_other_side) + elif data == 2: + composite.alpha_over(img, mirror_gate_side, (-1,7), mirror_gate_side) + composite.alpha_over(img, mirror_gate_side, (10,2), mirror_gate_side) + elif data == 3: + composite.alpha_over(img, mirror_gate_other_side, (2,1), mirror_gate_other_side) + composite.alpha_over(img, mirror_gate_other_side, (13,7), mirror_gate_other_side) + else: + # closed + + # positions for pasting the fence sides, as with fences + pos_top_left = (2,3) + pos_top_right = (10,3) + pos_bottom_right = (10,7) + pos_bottom_left = (2,7) + + if data == 0 or data == 2: + composite.alpha_over(img, gate_other_side, pos_top_right, gate_other_side) + composite.alpha_over(img, mirror_gate_other_side, pos_bottom_left, mirror_gate_other_side) + elif data == 1 or data == 3: + composite.alpha_over(img, gate_side, pos_top_left, gate_side) + composite.alpha_over(img, mirror_gate_side, pos_bottom_right, mirror_gate_side) + + return img + +# mycelium +block(blockid=110, top_index=78, side_index=77) + +# lilypad +# TODO the data-block orientation relation is not clear +@material(blockid=111, data=range(4), transparent=True) +def lilypad(blockid, data, north): + 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 + + t = terrain_images[76] # NOTE: using same data as stairs, no + # info in minepedia at the moment. + if data == 0: # pointing south + img = build_full_block(None, None, None, None, None, t) + elif data == 1: # pointing north + img = build_full_block(None, None, None, None, None, t.rotate(180)) + elif data == 2: # pointing west + img = build_full_block(None, None, None, None, None, t.rotate(270)) + elif data == 3: # pointing east + img = build_full_block(None, None, None, None, None, t.rotate(90)) + + return img + +# nether brick +block(blockid=112, top_index=224, side_index=224) + +# nether wart +@material(blockid=115, data=range(4), transparent=True) +def nether_wart(blockid, data): + if data == 0: # just come up + t = terrain_images[226] + elif data in (1, 2): + t = terrain_images[227] + else: # fully grown + t = terrain_images[228] + + # use the same technic as tall grass + img = build_billboard(t) + + return img + +# enchantment table +# TODO there's no book at the moment +@material(blockid=116, transparent=True) +def enchantment_table(blockid, data): + # no book at the moment + top = terrain_images[166] + side = terrain_images[182] + img = build_full_block((top, 4), None, None, side, side) + + return img + +# brewing stand +# TODO this is a place holder, is a 2d image pasted +@material(blockid=117, data=range(5), transparent=True) +def brewing_stand(blockid, data, north): + t = terrain_images[157] + img = build_billboard(t) + return img + +# cauldron +@material(blockid=118, data=range(4), transparent=True) +def cauldron(blockid, data): + side = terrain_images[154] + top = terrain_images[138] + bottom = terrain_images[139] + water = transform_image_top(_load_image("water.png")) + if data == 0: # empty + img = build_full_block(top, side, side, side, side) + if data == 1: # 1/3 filled + img = build_full_block(None , side, side, None, None) + composite.alpha_over(img, water, (0,8), water) + img2 = build_full_block(top , None, None, side, side) + composite.alpha_over(img, img2, (0,0), img2) + if data == 2: # 2/3 filled + img = build_full_block(None , side, side, None, None) + composite.alpha_over(img, water, (0,4), water) + img2 = build_full_block(top , None, None, side, side) + composite.alpha_over(img, img2, (0,0), img2) + if data == 3: # 3/3 filled + img = build_full_block(None , side, side, None, None) + composite.alpha_over(img, water, (0,0), water) + img2 = build_full_block(top , None, None, side, side) + composite.alpha_over(img, img2, (0,0), img2) + + return img + +# end portal +@material(blockid=119, transparent=True) +def end_portal(blockid, data): + img = Image.new("RGBA", (24,24), bgcolor) + # generate a black texure with white, blue and grey dots resembling stars + t = Image.new("RGBA", (16,16), (0,0,0,255)) + for color in [(155,155,155,255), (100,255,100,255), (255,255,255,255)]: + for i in range(6): + x = randint(0,15) + y = randint(0,15) + t.putpixel((x,y),color) + + t = transform_image_top(t) + composite.alpha_over(img, t, (0,0), t) + + return img + +# end portal frame +@material(blockid=120, data=range(5), transparent=True) +def end_porta_frame(blockid, data): + # The bottom 2 bits are oritation info but seems there is no + # graphical difference between orientations + top = terrain_images[158] + eye_t = terrain_images[174] + side = terrain_images[159] + img = build_full_block((top, 4), None, None, side, side) + if data & 0x4 == 0x4: # ender eye on it + # generate the eye + eye_t = terrain_images[174].copy() + eye_t_s = terrain_images[174].copy() + # cut out from the texture the side and the top of the eye + ImageDraw.Draw(eye_t).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(eye_t_s).rectangle((0,4,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + # trnasform images and paste + eye = transform_image_top(eye_t) + eye_s = transform_image_side(eye_t_s) + eye_os = eye_s.transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, eye_s, (5,5), eye_s) + composite.alpha_over(img, eye_os, (9,5), eye_os) + composite.alpha_over(img, eye, (0,0), eye) + + return img + +# end stone +block(blockid=121, top_index=175) From 6dbac888d2e28583148411d592e6f160fb6aa60b Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Mon, 7 Nov 2011 13:26:22 +0100 Subject: [PATCH 25/30] Fix holes in stairs. --- overviewer_core/textures.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 77a00d0..6c3ab5e 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -1430,6 +1430,7 @@ def stairs(blockid, data, north): tmp1 = ImageEnhance.Brightness(tmp1).enhance(0.8) tmp1.putalpha(sidealpha) + composite.alpha_over(img, tmp1, (6,4)) #workaround, fixes a hole composite.alpha_over(img, tmp1, (6,3)) tmp2 = transform_image_top(half_block_l) composite.alpha_over(img, tmp2, (0,6)) @@ -1458,6 +1459,7 @@ def stairs(blockid, data, north): tmp1 = ImageEnhance.Brightness(tmp1).enhance(0.7) tmp1.putalpha(sidealpha) + composite.alpha_over(img, tmp1, (6,4)) #workaround, fixes a hole composite.alpha_over(img, tmp1, (6,3)) tmp2 = transform_image_top(half_block_d) composite.alpha_over(img, tmp2, (0,6)) From 25493c071f206c0a3c53e7f8fd9532ffb8512582 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Mon, 7 Nov 2011 15:20:43 +0100 Subject: [PATCH 26/30] Add place holders for levers and buttons so the block is transparent. --- overviewer_core/textures.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 6c3ab5e..b984f69 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -1862,6 +1862,10 @@ def wall_sign(blockid, data, north): # wall sign ## ## not rendered: levers ## +@material(blockid=69, data=range(16), transparent=True) +def levers(blockid, data, north): + # place holder, used to mae the block transparent + return None # wooden and stone pressure plates @material(blockid=[70, 72], data=[0,1], transparent=True) @@ -1900,6 +1904,10 @@ block(blockid=[73, 74], top_index=51) ## ## not rendered: buttons ## +@material(blockid=77, data=range(16), transparent=True) +def buttons(blockid, data, north): + # place holder, used to make the block transparent + return None # snow @material(blockid=78, data=range(8), transparent=True) From 29bc7fa0ba3b1d9c025a33198f153a6ad37d0ca8 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Mon, 7 Nov 2011 15:41:55 +0100 Subject: [PATCH 27/30] Update iterate.c for nether brick fences and update the way glass panes and iron bars stick. --- overviewer_core/src/iterate.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/overviewer_core/src/iterate.c b/overviewer_core/src/iterate.c index 7d8dc23..c6c46a1 100644 --- a/overviewer_core/src/iterate.c +++ b/overviewer_core/src/iterate.c @@ -276,14 +276,15 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) { return final_data; - /* portal, iron bars and glass panes - * Note: iron bars and glass panes "stick" to other blocks, but - * at the moment of writing this is not clear which ones stick and - * which others no, so for the moment stick only with himself. - * This is a TODO! - */ - } else if ((state->block == 90) || (state->block == 101) || - (state->block == 102)) { + } else if ((state->block == 101) || (state->block == 102)) { + /* iron bars and glass panes: + * they seem to stick to almost everything but air, but + * not sure yet! Still a TODO! */ + /* return check adjacent blocks with air, bit inverted */ + return check_adjacent_blocks(state, x, y, z, 0) ^ 0x0f; + + } else if ((state->block == 90) || (state->block == 113)) { + /* portal and nether brick fences */ return check_adjacent_blocks(state, x, y, z, state->block); } @@ -414,7 +415,8 @@ chunk_render(PyObject *self, PyObject *args) { (state.block == 20) || (state.block == 54) || (state.block == 55) || (state.block == 79) || (state.block == 85) || (state.block == 90) || - (state.block == 101) || (state.block == 102)) { + (state.block == 101) || (state.block == 102) || + (state.block == 113)) { ancilData = generate_pseudo_data(&state, ancilData); state.block_pdata = ancilData; } else { From e2dde5e6e0ddf2b98492b58602e5f27390869bd3 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Mon, 7 Nov 2011 16:21:03 +0100 Subject: [PATCH 28/30] Add lily pads to biome tinting. Fix pumpkin and melon stem. --- overviewer_core/src/rendermode-normal.c | 11 ++++++++-- overviewer_core/textures.py | 29 +++++++++++-------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/overviewer_core/src/rendermode-normal.c b/overviewer_core/src/rendermode-normal.c index e37a47a..24bf665 100644 --- a/overviewer_core/src/rendermode-normal.c +++ b/overviewer_core/src/rendermode-normal.c @@ -173,7 +173,9 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * * get constant brown color (see textures.py) */ (((state->block == 104) || (state->block == 105)) && (state->block_data != 7)) || /* vines */ - state->block == 106) + state->block == 106 || + /* lily pads */ + state->block == 111) { /* do the biome stuff! */ PyObject *facemask = mask; @@ -240,6 +242,10 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * /* vines */ color = PySequence_GetItem(self->grasscolor, index); break; + case 111: + /* lily padas */ + color = PySequence_GetItem(self->grasscolor, index); + break; default: break; }; @@ -270,7 +276,8 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * facemask = NULL; } - if (state->block == 18 || state->block == 106) /* leaves and vines */ + if (state->block == 18 || state->block == 106 || state->block == 111) + /* leaves, vines and lyli pads */ { r = 37; g = 118; diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index b984f69..918940a 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -2509,23 +2509,20 @@ block(blockid=103, top_index=137, side_index=136, solid=True) def stem(blockid, data, north): # the ancildata value indicates how much of the texture # is shown. - if data & 7 == 0: - # not fully grown stem or no pumpkin/melon touching it, - # straight up stem - t = terrain_images[111].copy() - img = Image.new("RGBA", (16,16), bgcolor) - composite.alpha_over(img, t, (0, int(16 - 16*((data + 1)/8.))), t) - img = build_sprite(t) - if data & 7 == 7: - # fully grown stem gets brown color! - # there is a conditional in rendermode-normal to not - # tint the data value 7 - img = tintTexture(img, (211,169,116)) - return img + + # not fully grown stem or no pumpkin/melon touching it, + # straight up stem + t = terrain_images[111].copy() + img = Image.new("RGBA", (16,16), bgcolor) + composite.alpha_over(img, t, (0, int(16 - 16*((data + 1)/8.))), t) + img = build_sprite(t) + if data & 7 == 7: + # fully grown stem gets brown color! + # there is a conditional in rendermode-normal.c to not + # tint the data value 7 + img = tintTexture(img, (211,169,116)) + return img - else: # fully grown, and a pumpking/melon touching it, - # corner stem - return None # vines # TODO multiple sides of a block can contain vines! At the moment From 74e0ef2ad63a1c4e5622e6829720d6da85a55bcf Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Thu, 10 Nov 2011 09:42:21 -0500 Subject: [PATCH 29/30] brought solid_blocks (etc.) sets back in line with old values --- overviewer_core/textures.py | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 918940a..2bbbbb4 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -715,14 +715,14 @@ def saplings(blockid, data): # bedrock block(blockid=7, top_index=17) -@material(blockid=8, data=range(16), fluid=True, transparent=True) +@material(blockid=8, data=range(16), fluid=True, transparent=True, nospawn=True) def water(blockid, data): watertex = _load_image("water.png") return build_block(watertex, watertex) # other water, glass, and ice (no inner surfaces) # uses pseudo-ancildata found in iterate.c -@material(blockid=[9, 20, 79], data=range(32), fluid=(9,), transparent=True, nospawn=True) +@material(blockid=[9, 20, 79], data=range(32), fluid=(9,), transparent=True, nospawn=True, solid=(79, 20)) def no_inner_surfaces(blockid, data): if blockid == 9: texture = _load_image("water.png") @@ -763,7 +763,7 @@ def no_inner_surfaces(blockid, data): img = build_full_block(top,None,None,side3,side4) return img -@material(blockid=[10, 11], data=range(16), fluid=True, transparent=False) +@material(blockid=[10, 11], data=range(16), fluid=True, transparent=False, nospawn=True) def lava(blockid, data): lavatex = _load_image("lava.png") return build_block(lavatex, lavatex) @@ -846,7 +846,7 @@ block(blockid=24, top_index=176, side_index=192) # note block block(blockid=25, top_index=74) -@material(blockid=26, data=range(12), transparent=True) +@material(blockid=26, data=range(12), transparent=True, nospawn=True) def bed(blockid, data, north): # first get north rotation done # Masked to not clobber block head/foot info @@ -1000,7 +1000,7 @@ def rails(blockid, data, north): return img # sticky and normal piston body -@material(blockid=[29, 33], data=[0,1,2,3,4,5,8,9,10,11,12,13], transparent=True, solid=True) +@material(blockid=[29, 33], data=[0,1,2,3,4,5,8,9,10,11,12,13], transparent=True, solid=True, nospawn=True) def piston(blockid, data, north): # first, north rotation # Masked to not clobber block head/foot info @@ -1076,7 +1076,7 @@ def piston(blockid, data, north): return img # sticky and normal piston shaft -@material(blockid=34, data=[0,1,2,3,4,5,8,9,10,11,12,13], transparent=True) +@material(blockid=34, data=[0,1,2,3,4,5,8,9,10,11,12,13], transparent=True, nospawn=True) def piston_extension(blockid, data, north): # first, north rotation # Masked to not clobber block head/foot info @@ -1163,7 +1163,7 @@ def piston_extension(blockid, data, north): return img # cobweb -sprite(blockid=30, index=11) +sprite(blockid=30, index=11, nospawn=True) @material(blockid=31, data=range(3), transparent=True) def tall_grass(blockid, data): @@ -1230,7 +1230,7 @@ block(blockid=41, top_index=23) block(blockid=42, top_index=22) # double slabs and slabs -@material(blockid=[43, 44], data=range(6), transparent=(44,), solid=(43,)) +@material(blockid=[43, 44], data=range(6), transparent=(44,), solid=True) def slabs(blockid, data): if data == 0: # stone slab top = terrain_images[6] @@ -1277,7 +1277,7 @@ def slabs(blockid, data): # brick block block(blockid=45, top_index=7) # TNT -block(blockid=46, top_index=9, side_index=8) +block(blockid=46, top_index=9, side_index=8, nospawn=True) # bookshelf block(blockid=47, top_index=4, side_index=35) # moss stone @@ -1373,10 +1373,10 @@ def fire(blockid, data): return img # monster spawner -block(blockid=52, top_index=34) +block(blockid=52, top_index=34, transparent=True) # wooden, cobblestone, red brick, stone brick and netherbrick stairs. -@material(blockid=[53,67,108,109,114], data=range(4), transparent=True) +@material(blockid=[53,67,108,109,114], data=range(4), transparent=True, solid=True, nospawn=True) def stairs(blockid, data, north): # first, north rotations @@ -1471,7 +1471,7 @@ def stairs(blockid, data, north): # normal and locked chest (locked was the one used in april fools' day) # uses pseudo-ancildata found in iterate.c -@material(blockid=[54,95], data=range(12), transparent=True) +@material(blockid=[54,95], data=range(12), solid=True) def chests(blockid, data): # First two bits of the pseudo data store if it's a single chest # or it's a double chest, first half or second half (left to right). @@ -1613,7 +1613,7 @@ def crafting_table(blockid, data): return img # crops -@material(blockid=59, data=range(8), transparent=True) +@material(blockid=59, data=range(8), transparent=True, nospawn=True) def crops(blockid, data): raw_crop = terrain_images[88+data] crop1 = transform_image_top(raw_crop) @@ -1758,7 +1758,7 @@ def door(blockid, data, north): return img # ladder -@material(blockd=65, data=[2, 3, 4, 5], transparent = True) +@material(blockd=65, data=[2, 3, 4, 5], transparent=True) def ladder(blockid, data, north): # first north rotations @@ -1910,7 +1910,7 @@ def buttons(blockid, data, north): return None # snow -@material(blockid=78, data=range(8), transparent=True) +@material(blockid=78, data=range(8), transparent=True, solid=True) def snow(blockid, data): # still not rendered correctly: data other than 0 @@ -1977,7 +1977,7 @@ def jukebox(blockid, data): # nether and normal fences # uses pseudo-ancildata found in iterate.c -@material(blockid=[85, 113], data=range(16), transparent=True) +@material(blockid=[85, 113], data=range(16), transparent=True, nospawn=True) def fence(blockid, data): # no need for north rotations, it uses pseudo data. # create needed images for Big stick fence @@ -2138,7 +2138,7 @@ def portal(blockid, data): # cake! # TODO is rendered un-bitten -@material(blockid=92, data=range(6), transparent=True) +@material(blockid=92, data=range(6), transparent=True, nospawn=True) def cake(blockid, data): # choose textures for cake @@ -2167,7 +2167,7 @@ def cake(blockid, data): return img # redstone repeaters ON and OFF -@material(blockid=[93,94], data=range(16), transparent=True) +@material(blockid=[93,94], data=range(16), transparent=True, nospawn=True) def repeater(blockid, data, north): # north rotation # Masked to not clobber delay info @@ -2313,7 +2313,7 @@ def repeater(blockid, data, north): # trapdoor # TODO the trapdoor is looks like a sprite when opened, that's not good -@material(blockid=96, data=range(8), transparent=True) +@material(blockid=96, data=range(8), transparent=True, nospawn=True) def trapdoor(blockid, data, north): # north rotation @@ -2458,7 +2458,7 @@ def huge_mushroom(blockid, data, north): # iron bars and glass pane # TODO glass pane is not a sprite, it has a texture for the side, # at the moment is not used -@material(blockid=[101,102], data=range(16), transparent=True) +@material(blockid=[101,102], data=range(16), transparent=True, nospawn=True) def panes(blockid, data): # no north rotation, uses pseudo data if blockid == 101: @@ -2570,7 +2570,7 @@ def vines(blockid, data, north): return img # fence gates -@material(blockid=107, data=range(8), transparent=True) +@material(blockid=107, data=range(8), transparent=True, nospawn=True) def fence_gate(blockid, data, north): # north rotation From dc0110ee735a27651ab1d3f5ca33bc7bc5e35ad0 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Thu, 10 Nov 2011 09:51:25 -0500 Subject: [PATCH 30/30] unknown blocks are no longer assumed to be transparent --- overviewer_core/src/iterate.c | 8 ++++++++ overviewer_core/textures.py | 4 +++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/overviewer_core/src/iterate.c b/overviewer_core/src/iterate.c index c6c46a1..47e175b 100644 --- a/overviewer_core/src/iterate.c +++ b/overviewer_core/src/iterate.c @@ -20,6 +20,7 @@ static PyObject *textures = NULL; static PyObject *chunk_mod = NULL; static PyObject *blockmap = NULL; +static PyObject *known_blocks = NULL; static PyObject *transparent_blocks = NULL; PyObject *init_chunk_render(PyObject *self, PyObject *args) { @@ -45,6 +46,9 @@ PyObject *init_chunk_render(PyObject *self, PyObject *args) { blockmap = PyObject_GetAttrString(textures, "blockmap"); if (!blockmap) return NULL; + known_blocks = PyObject_GetAttrString(textures, "known_blocks"); + if (!known_blocks) + return NULL; transparent_blocks = PyObject_GetAttrString(textures, "transparent_blocks"); if (!transparent_blocks) return NULL; @@ -56,6 +60,10 @@ int is_transparent(unsigned char b) { PyObject *block = PyInt_FromLong(b); int ret = PySequence_Contains(transparent_blocks, block); + if (!ret) + { + ret = !(PySequence_Contains(known_blocks, block)); + } Py_DECREF(block); return ret; diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 2bbbbb4..39d4739 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -546,7 +546,8 @@ blockmap_generators = {} blockmap = {} biome_grass_texture = None -transparent_blocks = set([0,]) +known_blocks = set() +transparent_blocks = set() solid_blocks = set() fluid_blocks = set() nospawn_blocks = set() @@ -579,6 +580,7 @@ def material(blockid=[], data=[0], **kwargs): for block in blockid: # set the property sets appropriately + known_blocks.update([block]) for prop in properties: try: if block in kwargs.get(prop, []):