==================== Design Documentation ==================== So you'd like a technical overview of how The Overviewer works, huh? You've come to the right place! This document's scope does not cover the details of the code. The code is fairly well commented and not difficult to understand. Instead, this document is intended to give an explanation to how the Overviewer was designed, why certain decisions were made, and how all the pieces fit together. Think of this document as commenting on how all the high level pieces of the code work. This document is probably a good read to anyone that wants to get involved in Overviewer development. So let's get started! .. contents:: Background Info =============== The Overviewer's task is to take Minecraft worlds and render them into a set of tiles that can be displayed with a Google Maps interface. A Minecraft world extends indefinitely along the two horizontal axes, and are exactly 128 units high. Minecraft worlds are made of cubes, where each slot in the world's grid has a type that determines what it is (grass, stone, ...). This makes worlds relatively uncomplicated to render, the Overviewer simply determines what cubes to draw and where. Since everything in Minecraft is aligned to a strict grid, placement and rendering decisions are completely deterministic and can be performed in an iterative fashon. The coordinate system for Minecraft has three axes. The X and Z axes are the horizontal axes. They extend indefinitely towards both positive and negative infinity. (There are practical limits, but no theoretical limits). The Y axis extends from 0 to 127, which corresponds with the world height limit. Each block in Minecraft has a coordinate address, e.g. the block at 15,78,-35 refers to 15 along the X axis, -35 along the Z axis, and 78 units up from bedrock. The world is divided up into *chunks*. A chunk is a 16 by 16 area of the world that extends from bedrock to sky. In other words, a 16,128,16 "chunk" of the world. Chunks also have an address, but in only 2 dimensions. To find the which chunk a block is in, simply divide its X and Z coordinates by 16 and take the floor. Minecraft worlds are generated on-the-fly by the chunk. This means not all chunks will exist. There is no pattern to chunk generation, the game simply generates them as needed. Chunks are stored on-disk in region files. A Minecraft region is a "region" of 32 by 32 chunks. Regions have their own address, and for a particular chunk one can find its region by dividing its coordinates by 32 and taking the floor. A region may contain all 1024 of its chunks, or only a subset of them, since not all chunks may exist. The absence of a region file indicates none of its chunks exist. About the Rendering =================== Minecraft worlds are rendered in an approximated Isometric projection at an oblique angle. In the original design, the projection acts as if your eye is infinitely far away looking down at the world at a 45 degree angle in the South-East direction (now, the world can be rendered at any of the 4 oblique directions). .. image:: screenshot.png :alt: A screenshot of Overviewer output In order to render a Minecraft world, there are a few steps that need to happen. These steps are explained in detail in the next few sections. 1. Render each block 2. Render the chunks from the blocks 3. Render the tiles of the map from the chunks 4. Shrink and combine the tiles for the other zoom levels Block Rendering =============== .. This section shows how each block is pre-rendered The first step is rendering the blocks from the textures. Each block is "built" from its textures into an image of a cube and cached in global variables of the :mod:`textures` module. Textures come in the size 16 by 16 (higher resolution textures are resized and 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 :alt: A texture gets rendered into a cube .. _affine transformation: http://en.wikipedia.org/wiki/Affine_transformation The result is an image of a cube that is 24 by 24 pixels in size. This particular size for the cubes was chosen for an important reason: 24 is divisible by 2 and by 4. This makes placement much easier. E.g. in order to draw two cubes that are next to each other in the world, one is drawn exactly 12 pixels over and 6 pixels down from the other. All placements of the cubes happen on exact pixel boundaries and no further resolution is lost beyond the initial transformations. The transformation happens in two stages. First, the texture is transformed for the top of the cube. Then the texture is transformed for the left side of the cube, which is mirrored for the right side of the cube. Top Transformation ------------------ The transformation for the top face of the cube is a simple `affine transformation`_ from the original square texture. It is actually several affine transformations: a re-size, a rotation, and a scaling; but since multiple affine transformations can be chained together simply by multiplying the transformation matrices together, only one transformation is actually done. This can be seen in the function :func:`textures.transform_image`. It takes these steps: 1. The texture is re-sized to 17 by 17 pixels. This is done because the diagonal of a square with sides 17 is approximately 24, which is the target size for the bounding box of the cube image. So when it's rotated, it will be the correct width. 2. The image is rotated 45 degrees about its center. 3. The image is scaled on the vertical axis by a factor of 1/2. This produces an image of size 24 by 12 as seen in the following sequence. .. image:: 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:: dirt_top.png :alt: Top of dirt On the left is the top of the dirt block at actual size after the transformation, the right is the same but blown up by a factor of 10 with no interpolation to show the pixels. Side Transformation ------------------- The texture square is transformed for the sides of the cube in the :func:`textures.transform_image_side` function. This is another `affine transformation`_, but this time only two transformations are done: a re-size and a shear. 1. First the texture is re-sized to 12 by 12 pixels. This is half the width of 24 so it will have the correct width after the 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 :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:: dirt_side.png :alt: The sides of the dirt block Again, the left are the two sides of the dirt block at actual size, the right is scaled with no interpolation by a factor of 10 to show the pixels. An Entire Cube -------------- These three images, the top and two sides, are pasted into a single 24 by 24 pixel image to get the cube, as shown. 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 :alt: How the cube parts fit together There is one more complication. The cubes don't tessellate perfectly. This 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 :alt: Cubes don't tessellate perfectly The solution is to manually touch up those 6 pixels. 3 pixels are added on the upper left of each cube, 3 on the lower right. Therefore, they all line up perfectly! This is done at the end of :func:`textures._build_block` .. image:: pixelfix.png :alt: The 6 pixels manually added to each cube. Other Cube Types ---------------- Chunk Rendering =============== .. This goes over the rendering of a chunk Tile Rendering ============== .. Covers the placement of chunk images on a tile Reading the Data Files ====================== .. Covers how to extract the blocks of each chunk from the region files. Also covers the nbt file stuff. Image Composition ================= .. Covers the issues I had with PIL's image composition and why we needed something fancier. Multiprocessing =============== .. Covers how the Overviewer utilizes multiple processors to render faster Caching ======= .. How the overviewer determines what needs to be rendered and what doesn't Lighting ======== Cave Mode =========