// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3
using System;
using ClassicalSharp.GraphicsAPI;
using ClassicalSharp.Map;
using ClassicalSharp.Textures;
using OpenTK;
using BlockID = System.UInt16;

namespace ClassicalSharp {

	public unsafe sealed class AdvLightingMeshBuilder : ChunkMeshBuilder {
		
		Vector3 minBB, maxBB;
		bool isTranslucent;
		int initBitFlags, lightFlags;
		float x1, y1, z1, x2, y2, z2;
		
		protected override int StretchXLiquid(int countIndex, int x, int y, int z, int chunkIndex, BlockID block) {
			if (OccludedLiquid(chunkIndex)) return 0;
			initBitFlags = ComputeLightFlags(x, y, z, chunkIndex);
			bitFlags[chunkIndex] = initBitFlags;
			
			int count = 1;
			x++;
			chunkIndex++;
			countIndex += Side.Sides;
			bool stretchTile = (BlockInfo.CanStretch[block] & (1 << Side.Top)) != 0;
			
			while (x < chunkEndX && stretchTile && CanStretch(block, chunkIndex, x, y, z, Side.Top) && !OccludedLiquid(chunkIndex)) {
				counts[countIndex] = 0;
				count++;
				x++;
				chunkIndex++;
				countIndex += Side.Sides;
			}
			return count;
		}
		
		protected override int StretchX(int countIndex, int x, int y, int z, int chunkIndex, BlockID block, int face) {
			initBitFlags = ComputeLightFlags(x, y, z, chunkIndex);
			bitFlags[chunkIndex] = initBitFlags;
			
			int count = 1;
			x++;
			chunkIndex++;
			countIndex += Side.Sides;
			bool stretchTile = (BlockInfo.CanStretch[block] & (1 << face)) != 0;
			
			while (x < chunkEndX && stretchTile && CanStretch(block, chunkIndex, x, y, z, face)) {
				counts[countIndex] = 0;
				count++;
				x++;
				chunkIndex++;
				countIndex += Side.Sides;
			}
			return count;
		}
		
		protected override int StretchZ(int countIndex, int x, int y, int z, int chunkIndex, BlockID block, int face) {
			initBitFlags = ComputeLightFlags(x, y, z, chunkIndex);
			bitFlags[chunkIndex] = initBitFlags;
			
			int count = 1;
			z++;
			chunkIndex += extChunkSize;
			countIndex += chunkSize * Side.Sides;
			bool stretchTile = (BlockInfo.CanStretch[block] & (1 << face)) != 0;
			
			while (z < chunkEndZ && stretchTile && CanStretch(block, chunkIndex, x, y, z, face)) {
				counts[countIndex] = 0;
				count++;
				z++;
				chunkIndex += extChunkSize;
				countIndex += chunkSize * Side.Sides;
			}
			return count;
		}
		
		bool CanStretch(BlockID initialBlock, int chunkIndex, int x, int y, int z, int face) {
			BlockID rawBlock = chunk[chunkIndex];
			bitFlags[chunkIndex] = ComputeLightFlags(x, y, z, chunkIndex);
			return rawBlock == initialBlock
				&& !BlockInfo.IsFaceHidden(rawBlock, chunk[chunkIndex + offsets[face]], face)
				&& (initBitFlags == bitFlags[chunkIndex]
				    // Check that this face is either fully bright or fully in shadow
				    && (initBitFlags == 0 || (initBitFlags & masks[face]) == masks[face]));
		}
		
		
		protected override void RenderTile(int index) {
			if (BlockInfo.Draw[curBlock] == DrawType.Sprite) {
				fullBright = BlockInfo.FullBright[curBlock];
				tinted = BlockInfo.Tinted[curBlock];
				int count = counts[index + Side.Top];
				if (count != 0) DrawSprite(count);
				return;
			}
			
			int leftCount = counts[index++], rightCount = counts[index++],
			frontCount = counts[index++], backCount = counts[index++],
			bottomCount = counts[index++], topCount = counts[index++];
			if (leftCount == 0 && rightCount == 0 && frontCount == 0 &&
			    backCount == 0 && bottomCount == 0 && topCount == 0) return;
			
			fullBright = BlockInfo.FullBright[curBlock];
			isTranslucent = BlockInfo.Draw[curBlock] == DrawType.Translucent;
			lightFlags = BlockInfo.LightOffset[curBlock];
			tinted = BlockInfo.Tinted[curBlock];
			
			Vector3 min = BlockInfo.RenderMinBB[curBlock], max = BlockInfo.RenderMaxBB[curBlock];
			x1 = X + min.X; y1 = Y + min.Y; z1 = Z + min.Z;
			x2 = X + max.X; y2 = Y + max.Y; z2 = Z + max.Z;
			
			this.minBB = BlockInfo.MinBB[curBlock]; this.maxBB = BlockInfo.MaxBB[curBlock];
			minBB.Y = 1 - minBB.Y; maxBB.Y = 1 - maxBB.Y;
			
			if (leftCount != 0) DrawLeftFace(leftCount);
			if (rightCount != 0) DrawRightFace(rightCount);
			if (frontCount != 0) DrawFrontFace(frontCount);
			if (backCount != 0) DrawBackFace(backCount);
			if (bottomCount != 0) DrawBottomFace(bottomCount);
			if (topCount != 0) DrawTopFace(topCount);
		}
		
		
		void DrawLeftFace(int count) {
			int texId = BlockInfo.textures[curBlock * Side.Sides + Side.Left];
			int i = texId / Atlas1D.TilesPerAtlas;
			float vOrigin = (texId % Atlas1D.TilesPerAtlas) * Atlas1D.invTileSize;
			int offset = (lightFlags >> Side.Left) & 1;
			
			float u1 = minBB.Z, u2 = (count - 1) + maxBB.Z * 15.99f/16f;
			float v1 = vOrigin + maxBB.Y * Atlas1D.invTileSize;
			float v2 = vOrigin + minBB.Y * Atlas1D.invTileSize * 15.99f/16f;
			DrawInfo part = isTranslucent ? translucentParts[i] : normalParts[i];
			
			int F = bitFlags[cIndex];
			int aY0_Z0 = ((F >> xM1_yM1_zM1) & 1) + ((F >> xM1_yCC_zM1) & 1) + ((F >> xM1_yM1_zCC) & 1) + ((F >> xM1_yCC_zCC) & 1);
			int aY0_Z1 = ((F >> xM1_yM1_zP1) & 1) + ((F >> xM1_yCC_zP1) & 1) + ((F >> xM1_yM1_zCC) & 1) + ((F >> xM1_yCC_zCC) & 1);
			int aY1_Z0 = ((F >> xM1_yP1_zM1) & 1) + ((F >> xM1_yCC_zM1) & 1) + ((F >> xM1_yP1_zCC) & 1) + ((F >> xM1_yCC_zCC) & 1);
			int aY1_Z1 = ((F >> xM1_yP1_zP1) & 1) + ((F >> xM1_yCC_zP1) & 1) + ((F >> xM1_yP1_zCC) & 1) + ((F >> xM1_yCC_zCC) & 1);
			
			PackedCol col0_0 = fullBright ? PackedCol.White : lerpX[aY0_Z0], col1_0 = fullBright ? PackedCol.White : lerpX[aY1_Z0];
			PackedCol col1_1 = fullBright ? PackedCol.White : lerpX[aY1_Z1], col0_1 = fullBright ? PackedCol.White : lerpX[aY0_Z1];
			if (tinted) {
				PackedCol tint = BlockInfo.FogCol[curBlock];
				col0_0 *= tint; col1_0 *= tint; col1_1 *= tint; col0_1 *= tint;
			}
			
			int index = part.vIndex[Side.Left];
			if (aY0_Z0 + aY1_Z1 > aY0_Z1 + aY1_Z0) {
				vertices[index    ] = new VertexP3fT2fC4b(x1, y2, z1, u1, v1, col1_0);
				vertices[index + 1] = new VertexP3fT2fC4b(x1, y1, z1, u1, v2, col0_0);
				vertices[index + 2] = new VertexP3fT2fC4b(x1, y1, z2 + (count - 1), u2, v2, col0_1);
				vertices[index + 3] = new VertexP3fT2fC4b(x1, y2, z2 + (count - 1), u2, v1, col1_1);
			} else {
				vertices[index    ] = new VertexP3fT2fC4b(x1, y2, z2 + (count - 1), u2, v1, col1_1);
				vertices[index + 1] = new VertexP3fT2fC4b(x1, y2, z1, u1, v1, col1_0);
				vertices[index + 2] = new VertexP3fT2fC4b(x1, y1, z1, u1, v2, col0_0);
				vertices[index + 3] = new VertexP3fT2fC4b(x1, y1, z2 + (count - 1), u2, v2, col0_1);
			}
			part.vIndex[Side.Left] += 4;
		}

		void DrawRightFace(int count) {
			int texId = BlockInfo.textures[curBlock * Side.Sides + Side.Right];
			int i = texId / Atlas1D.TilesPerAtlas;
			float vOrigin = (texId % Atlas1D.TilesPerAtlas) * Atlas1D.invTileSize;
			int offset = (lightFlags >> Side.Right) & 1;
			
			float u1 = (count - minBB.Z), u2 = (1 - maxBB.Z) * 15.99f/16f;
			float v1 = vOrigin + maxBB.Y * Atlas1D.invTileSize;
			float v2 = vOrigin + minBB.Y * Atlas1D.invTileSize * 15.99f/16f;
			DrawInfo part = isTranslucent ? translucentParts[i] : normalParts[i];
			
			int F = bitFlags[cIndex];
			int aY0_Z0 = ((F >> xP1_yM1_zM1) & 1) + ((F >> xP1_yCC_zM1) & 1) + ((F >> xP1_yM1_zCC) & 1) + ((F >> xP1_yCC_zCC) & 1);
			int aY0_Z1 = ((F >> xP1_yM1_zP1) & 1) + ((F >> xP1_yCC_zP1) & 1) + ((F >> xP1_yM1_zCC) & 1) + ((F >> xP1_yCC_zCC) & 1);
			int aY1_Z0 = ((F >> xP1_yP1_zM1) & 1) + ((F >> xP1_yCC_zM1) & 1) + ((F >> xP1_yP1_zCC) & 1) + ((F >> xP1_yCC_zCC) & 1);
			int aY1_Z1 = ((F >> xP1_yP1_zP1) & 1) + ((F >> xP1_yCC_zP1) & 1) + ((F >> xP1_yP1_zCC) & 1) + ((F >> xP1_yCC_zCC) & 1);
			
			PackedCol col0_0 = fullBright ? PackedCol.White : lerpX[aY0_Z0], col1_0 = fullBright ? PackedCol.White : lerpX[aY1_Z0];
			PackedCol col1_1 = fullBright ? PackedCol.White : lerpX[aY1_Z1], col0_1 = fullBright ? PackedCol.White : lerpX[aY0_Z1];
			if (tinted) {
				PackedCol tint = BlockInfo.FogCol[curBlock];
				col0_0 *= tint; col1_0 *= tint; col1_1 *= tint; col0_1 *= tint;
			}
			
			int index = part.vIndex[Side.Right];
			if (aY0_Z0 + aY1_Z1 > aY0_Z1 + aY1_Z0) {
				vertices[index    ] = new VertexP3fT2fC4b(x2, y2, z1, u1, v1, col1_0);
				vertices[index + 1] = new VertexP3fT2fC4b(x2, y2, z2 + (count - 1), u2, v1, col1_1);
				vertices[index + 2] = new VertexP3fT2fC4b(x2, y1, z2 + (count - 1), u2, v2, col0_1);
				vertices[index + 3] = new VertexP3fT2fC4b(x2, y1, z1, u1, v2, col0_0);
			} else {
				vertices[index    ] = new VertexP3fT2fC4b(x2, y2, z2 + (count - 1), u2, v1, col1_1);
				vertices[index + 1] = new VertexP3fT2fC4b(x2, y1, z2 + (count - 1), u2, v2, col0_1);
				vertices[index + 2] = new VertexP3fT2fC4b(x2, y1, z1, u1, v2, col0_0);
				vertices[index + 3] = new VertexP3fT2fC4b(x2, y2, z1, u1, v1, col1_0);
			}
			part.vIndex[Side.Right] += 4;
		}

		void DrawFrontFace(int count) {
			int texId = BlockInfo.textures[curBlock * Side.Sides + Side.Front];
			int i = texId / Atlas1D.TilesPerAtlas;
			float vOrigin = (texId % Atlas1D.TilesPerAtlas) * Atlas1D.invTileSize;
			int offset = (lightFlags >> Side.Front) & 1;
			
			float u1 = (count - minBB.X), u2 = (1 - maxBB.X) * 15.99f/16f;
			float v1 = vOrigin + maxBB.Y * Atlas1D.invTileSize;
			float v2 = vOrigin + minBB.Y * Atlas1D.invTileSize * 15.99f/16f;
			DrawInfo part = isTranslucent ? translucentParts[i] : normalParts[i];
			
			int F = bitFlags[cIndex];
			int aX0_Y0 = ((F >> xM1_yM1_zM1) & 1) + ((F >> xM1_yCC_zM1) & 1) + ((F >> xCC_yM1_zM1) & 1) + ((F >> xCC_yCC_zM1) & 1);
			int aX0_Y1 = ((F >> xM1_yP1_zM1) & 1) + ((F >> xM1_yCC_zM1) & 1) + ((F >> xCC_yP1_zM1) & 1) + ((F >> xCC_yCC_zM1) & 1);
			int aX1_Y0 = ((F >> xP1_yM1_zM1) & 1) + ((F >> xP1_yCC_zM1) & 1) + ((F >> xCC_yM1_zM1) & 1) + ((F >> xCC_yCC_zM1) & 1);
			int aX1_Y1 = ((F >> xP1_yP1_zM1) & 1) + ((F >> xP1_yCC_zM1) & 1) + ((F >> xCC_yP1_zM1) & 1) + ((F >> xCC_yCC_zM1) & 1);
			
			PackedCol col0_0 = fullBright ? PackedCol.White : lerpZ[aX0_Y0], col1_0 = fullBright ? PackedCol.White : lerpZ[aX1_Y0];
			PackedCol col1_1 = fullBright ? PackedCol.White : lerpZ[aX1_Y1], col0_1 = fullBright ? PackedCol.White : lerpZ[aX0_Y1];
			if (tinted) {
				PackedCol tint = BlockInfo.FogCol[curBlock];
				col0_0 *= tint; col1_0 *= tint; col1_1 *= tint; col0_1 *= tint;
			}
			
			int index = part.vIndex[Side.Front];
			if (aX1_Y1 + aX0_Y0 > aX0_Y1 + aX1_Y0) {
				vertices[index    ] = new VertexP3fT2fC4b(x2 + (count - 1), y1, z1, u2, v2, col1_0);
				vertices[index + 1] = new VertexP3fT2fC4b(x1, y1, z1, u1, v2, col0_0);
				vertices[index + 2] = new VertexP3fT2fC4b(x1, y2, z1, u1, v1, col0_1);
				vertices[index + 3] = new VertexP3fT2fC4b(x2 + (count - 1), y2, z1, u2, v1, col1_1);
			} else {
				vertices[index    ] = new VertexP3fT2fC4b(x1, y1, z1, u1, v2, col0_0);
				vertices[index + 1] = new VertexP3fT2fC4b(x1, y2, z1, u1, v1, col0_1);
				vertices[index + 2] = new VertexP3fT2fC4b(x2 + (count - 1), y2, z1, u2, v1, col1_1);
				vertices[index + 3] = new VertexP3fT2fC4b(x2 + (count - 1), y1, z1, u2, v2, col1_0);
			}
			part.vIndex[Side.Front] += 4;
		}
		
		void DrawBackFace(int count) {
			int texId = BlockInfo.textures[curBlock * Side.Sides + Side.Back];
			int i = texId / Atlas1D.TilesPerAtlas;
			float vOrigin = (texId % Atlas1D.TilesPerAtlas) * Atlas1D.invTileSize;
			int offset = (lightFlags >> Side.Back) & 1;
			
			float u1 = minBB.X, u2 = (count - 1) + maxBB.X * 15.99f/16f;
			float v1 = vOrigin + maxBB.Y * Atlas1D.invTileSize;
			float v2 = vOrigin + minBB.Y * Atlas1D.invTileSize * 15.99f/16f;
			DrawInfo part = isTranslucent ? translucentParts[i] : normalParts[i];
			
			int F = bitFlags[cIndex];
			int aX0_Y0 = ((F >> xM1_yM1_zP1) & 1) + ((F >> xM1_yCC_zP1) & 1) + ((F >> xCC_yM1_zP1) & 1) + ((F >> xCC_yCC_zP1) & 1);
			int aX1_Y0 = ((F >> xP1_yM1_zP1) & 1) + ((F >> xP1_yCC_zP1) & 1) + ((F >> xCC_yM1_zP1) & 1) + ((F >> xCC_yCC_zP1) & 1);
			int aX0_Y1 = ((F >> xM1_yP1_zP1) & 1) + ((F >> xM1_yCC_zP1) & 1) + ((F >> xCC_yP1_zP1) & 1) + ((F >> xCC_yCC_zP1) & 1);
			int aX1_Y1 = ((F >> xP1_yP1_zP1) & 1) + ((F >> xP1_yCC_zP1) & 1) + ((F >> xCC_yP1_zP1) & 1) + ((F >> xCC_yCC_zP1) & 1);
			
			PackedCol col1_1 = fullBright ? PackedCol.White : lerpZ[aX1_Y1], col1_0 = fullBright ? PackedCol.White : lerpZ[aX1_Y0];
			PackedCol col0_0 = fullBright ? PackedCol.White : lerpZ[aX0_Y0], col0_1 = fullBright ? PackedCol.White : lerpZ[aX0_Y1];
			if (tinted) {
				PackedCol tint = BlockInfo.FogCol[curBlock];
				col0_0 *= tint; col1_0 *= tint; col1_1 *= tint; col0_1 *= tint;
			}
			
			int index = part.vIndex[Side.Back];
			if (aX1_Y1 + aX0_Y0 > aX0_Y1 + aX1_Y0) {
				vertices[index    ] = new VertexP3fT2fC4b(x1, y2, z2, u1, v1, col0_1);
				vertices[index + 1] = new VertexP3fT2fC4b(x1, y1, z2, u1, v2, col0_0);
				vertices[index + 2] = new VertexP3fT2fC4b(x2 + (count - 1), y1, z2, u2, v2, col1_0);
				vertices[index + 3] = new VertexP3fT2fC4b(x2 + (count - 1), y2, z2, u2, v1, col1_1);
			} else {
				vertices[index    ] = new VertexP3fT2fC4b(x2 + (count - 1), y2, z2, u2, v1, col1_1);
				vertices[index + 1] = new VertexP3fT2fC4b(x1, y2, z2, u1, v1, col0_1);
				vertices[index + 2] = new VertexP3fT2fC4b(x1, y1, z2, u1, v2, col0_0);
				vertices[index + 3] = new VertexP3fT2fC4b(x2 + (count - 1), y1, z2, u2, v2, col1_0);
			}
			part.vIndex[Side.Back] += 4;
		}
		
		void DrawBottomFace(int count) {
			int texId = BlockInfo.textures[curBlock * Side.Sides + Side.Bottom];
			int i = texId / Atlas1D.TilesPerAtlas;
			float vOrigin = (texId % Atlas1D.TilesPerAtlas) * Atlas1D.invTileSize;
			int offset = (lightFlags >> Side.Bottom) & 1;
			
			float u1 = minBB.X, u2 = (count - 1) + maxBB.X * 15.99f/16f;
			float v1 = vOrigin + minBB.Z * Atlas1D.invTileSize;
			float v2 = vOrigin + maxBB.Z * Atlas1D.invTileSize * 15.99f/16f;
			DrawInfo part = isTranslucent ? translucentParts[i] : normalParts[i];
			
			int F = bitFlags[cIndex];
			int aX0_Z0 = ((F >> xM1_yM1_zM1) & 1) + ((F >> xM1_yM1_zCC) & 1) + ((F >> xCC_yM1_zM1) & 1) + ((F >> xCC_yM1_zCC) & 1);
			int aX1_Z0 = ((F >> xP1_yM1_zM1) & 1) + ((F >> xP1_yM1_zCC) & 1) + ((F >> xCC_yM1_zM1) & 1) + ((F >> xCC_yM1_zCC) & 1);
			int aX0_Z1 = ((F >> xM1_yM1_zP1) & 1) + ((F >> xM1_yM1_zCC) & 1) + ((F >> xCC_yM1_zP1) & 1) + ((F >> xCC_yM1_zCC) & 1);
			int aX1_Z1 = ((F >> xP1_yM1_zP1) & 1) + ((F >> xP1_yM1_zCC) & 1) + ((F >> xCC_yM1_zP1) & 1) + ((F >> xCC_yM1_zCC) & 1);
			
			PackedCol col0_1 = fullBright ? PackedCol.White : lerpY[aX0_Z1], col1_1 = fullBright ? PackedCol.White : lerpY[aX1_Z1];
			PackedCol col1_0 = fullBright ? PackedCol.White : lerpY[aX1_Z0], col0_0 = fullBright ? PackedCol.White : lerpY[aX0_Z0];
			if (tinted) {
				PackedCol tint = BlockInfo.FogCol[curBlock];
				col0_0 *= tint; col1_0 *= tint; col1_1 *= tint; col0_1 *= tint;
			}
			
			int index = part.vIndex[Side.Bottom];
			if (aX0_Z1 + aX1_Z0 > aX0_Z0 + aX1_Z1) {
				vertices[index    ] = new VertexP3fT2fC4b(x2 + (count - 1), y1, z2, u2, v2, col1_1);
				vertices[index + 1] = new VertexP3fT2fC4b(x1, y1, z2, u1, v2, col0_1);
				vertices[index + 2] = new VertexP3fT2fC4b(x1, y1, z1, u1, v1, col0_0);
				vertices[index + 3] = new VertexP3fT2fC4b(x2 + (count - 1), y1, z1, u2, v1, col1_0);
			} else {
				vertices[index    ] = new VertexP3fT2fC4b(x1, y1, z2, u1, v2, col0_1);
				vertices[index + 1] = new VertexP3fT2fC4b(x1, y1, z1, u1, v1, col0_0);
				vertices[index + 2] = new VertexP3fT2fC4b(x2 + (count - 1), y1, z1, u2, v1, col1_0);
				vertices[index + 3] = new VertexP3fT2fC4b(x2 + (count - 1), y1, z2, u2, v2, col1_1);
			}
			part.vIndex[Side.Bottom] += 4;
		}

		void DrawTopFace(int count) {
			int texId = BlockInfo.textures[curBlock * Side.Sides + Side.Top];
			int i = texId / Atlas1D.TilesPerAtlas;
			float vOrigin = (texId % Atlas1D.TilesPerAtlas) * Atlas1D.invTileSize;
			int offset = (lightFlags >> Side.Top) & 1;

			float u1 = minBB.X, u2 = (count - 1) + maxBB.X * 15.99f/16f;
			float v1 = vOrigin + minBB.Z * Atlas1D.invTileSize;
			float v2 = vOrigin + maxBB.Z * Atlas1D.invTileSize * 15.99f/16f;
			DrawInfo part = isTranslucent ? translucentParts[i] : normalParts[i];
			
			int F = bitFlags[cIndex];
			int aX0_Z0 = ((F >> xM1_yP1_zM1) & 1) + ((F >> xM1_yP1_zCC) & 1) + ((F >> xCC_yP1_zM1) & 1) + ((F >> xCC_yP1_zCC) & 1);
			int aX1_Z0 = ((F >> xP1_yP1_zM1) & 1) + ((F >> xP1_yP1_zCC) & 1) + ((F >> xCC_yP1_zM1) & 1) + ((F >> xCC_yP1_zCC) & 1);
			int aX0_Z1 = ((F >> xM1_yP1_zP1) & 1) + ((F >> xM1_yP1_zCC) & 1) + ((F >> xCC_yP1_zP1) & 1) + ((F >> xCC_yP1_zCC) & 1);
			int aX1_Z1 = ((F >> xP1_yP1_zP1) & 1) + ((F >> xP1_yP1_zCC) & 1) + ((F >> xCC_yP1_zP1) & 1) + ((F >> xCC_yP1_zCC) & 1);
			
			PackedCol col0_0 = fullBright ? PackedCol.White : lerp[aX0_Z0], col1_0 = fullBright ? PackedCol.White : lerp[aX1_Z0];
			PackedCol col1_1 = fullBright ? PackedCol.White : lerp[aX1_Z1], col0_1 = fullBright ? PackedCol.White : lerp[aX0_Z1];
			if (tinted) {
				PackedCol tint = BlockInfo.FogCol[curBlock];
				col0_0 *= tint; col1_0 *= tint; col1_1 *= tint; col0_1 *= tint;
			}
			
			int index = part.vIndex[Side.Top];
			if (aX0_Z0 + aX1_Z1 > aX0_Z1 + aX1_Z0) {
				vertices[index    ] = new VertexP3fT2fC4b(x2 + (count - 1), y2, z1, u2, v1, col1_0);
				vertices[index + 1] = new VertexP3fT2fC4b(x1, y2, z1, u1, v1, col0_0);
				vertices[index + 2] = new VertexP3fT2fC4b(x1, y2, z2, u1, v2, col0_1);
				vertices[index + 3] = new VertexP3fT2fC4b(x2 + (count - 1), y2, z2, u2, v2, col1_1);
			} else {
				vertices[index    ] = new VertexP3fT2fC4b(x1, y2, z1, u1, v1, col0_0);
				vertices[index + 1] = new VertexP3fT2fC4b(x1, y2, z2, u1, v2, col0_1);
				vertices[index + 2] = new VertexP3fT2fC4b(x2 + (count - 1), y2, z2, u2, v2, col1_1);
				vertices[index + 3] = new VertexP3fT2fC4b(x2 + (count - 1), y2, z1, u2, v1, col1_0);
			}
			part.vIndex[Side.Top] += 4;
		}
		
		PackedCol[] lerp = new PackedCol[5];
		PackedCol[] lerpX = new PackedCol[5];
		PackedCol[] lerpZ = new PackedCol[5];
		PackedCol[] lerpY = new PackedCol[5];
		protected override void PreStretchTiles(int x1, int y1, int z1) {
			base.PreStretchTiles(x1, y1, z1);
			for (int i = 0; i <= 4; i++) {
				lerp[i]  = PackedCol.Lerp(env.Shadow, env.Sun, i / 4f);
				lerpX[i] = PackedCol.Lerp(env.ShadowXSide, env.SunXSide, i / 4f);
				lerpZ[i] = PackedCol.Lerp(env.ShadowZSide, env.SunZSide, i / 4f);
				lerpY[i] = PackedCol.Lerp(env.ShadowYBottom, env.SunYBottom, i / 4f);
			}
		}
		
		
		#region Light computation
		
		int ComputeLightFlags(int x, int y, int z, int cIndex) {
			if (fullBright) return (1 << xP1_yP1_zP1) - 1; // all faces fully bright

			return
				Lit(x - 1, y, z - 1, cIndex - 1 - 18) << xM1_yM1_zM1 |
				Lit(x - 1, y, z,     cIndex - 1)      << xM1_yM1_zCC |
				Lit(x - 1, y, z + 1, cIndex - 1 + 18) << xM1_yM1_zP1 |
				Lit(x, y, z - 1,     cIndex + 0 - 18) << xCC_yM1_zM1 |
				Lit(x, y, z,         cIndex + 0)      << xCC_yM1_zCC |
				Lit(x, y, z + 1 ,    cIndex + 0 + 18) << xCC_yM1_zP1 |
				Lit(x + 1, y, z - 1, cIndex + 1 - 18) << xP1_yM1_zM1 |
				Lit(x + 1, y, z,     cIndex + 1)      << xP1_yM1_zCC |
				Lit(x + 1, y, z + 1, cIndex + 1 + 18) << xP1_yM1_zP1 ;
		}
		
		const int xM1_yM1_zM1 = 0,  xM1_yCC_zM1 = 1,  xM1_yP1_zM1 = 2;
		const int xCC_yM1_zM1 = 3,  xCC_yCC_zM1 = 4,  xCC_yP1_zM1 = 5;
		const int xP1_yM1_zM1 = 6,  xP1_yCC_zM1 = 7,  xP1_yP1_zM1 = 8;

		const int xM1_yM1_zCC = 9,  xM1_yCC_zCC = 10, xM1_yP1_zCC = 11;
		const int xCC_yM1_zCC = 12, xCC_yCC_zCC = 13, xCC_yP1_zCC = 14;
		const int xP1_yM1_zCC = 15, xP1_yCC_zCC = 16, xP1_yP1_zCC = 17;

		const int xM1_yM1_zP1 = 18, xM1_yCC_zP1 = 19, xM1_yP1_zP1 = 20;
		const int xCC_yM1_zP1 = 21, xCC_yCC_zP1 = 22, xCC_yP1_zP1 = 23;
		const int xP1_yM1_zP1 = 24, xP1_yCC_zP1 = 25, xP1_yP1_zP1 = 26;
		
		int Lit(int x, int y, int z, int cIndex) {
			if (y < 0 || y >= height) return 7; // all faces lit
			
			// TODO: check sides height (if sides > edges), check if edge block casts a shadow
			if (x < 0 || z < 0 || x >= width || z >= length) {
				return y >= edgeLevel ? 7 : y == (edgeLevel - 1) ? 6 : 0;
			}
			
			int flags = 0;
			BlockID block = chunk[cIndex];
			int lightHeight = light.heightmap[(z * width) + x];
			lightFlags = BlockInfo.LightOffset[block];

			// Use fact Light(Y.Bottom) == Light((Y - 1).Top)
			int offset = (lightFlags >> Side.Bottom) & 1;
			flags |= ((y - offset) > lightHeight ? 1 : 0);
			
			// Light is same for all the horizontal faces
			flags |= (y > lightHeight ? 2 : 0);
			
			// Use fact Light((Y + 1).Bottom) == Light(Y.Top)
			offset = (lightFlags >> Side.Top) & 1;
			flags |= ((y - offset) >= lightHeight ? 4 : 0);
			
			// Dynamic lighting
			if (BlockInfo.FullBright[block])               flags |= 5;
			if (BlockInfo.FullBright[chunk[cIndex + 324]]) flags |= 4;
			if (BlockInfo.FullBright[chunk[cIndex - 324]]) flags |= 1;
			return flags;
		}
		
		static int[] masks = new int[] {
			// Left face
			(1 << xM1_yM1_zM1) | (1 << xM1_yM1_zCC) | (1 << xM1_yM1_zP1) |
				(1 << xM1_yCC_zM1) | (1 << xM1_yCC_zCC) | (1 << xM1_yCC_zP1) |
				(1 << xM1_yP1_zM1) | (1 << xM1_yP1_zCC) | (1 << xM1_yP1_zP1),
			// Right face
			(1 << xP1_yM1_zM1) | (1 << xP1_yM1_zCC) | (1 << xP1_yM1_zP1) |
				(1 << xP1_yP1_zM1) | (1 << xP1_yP1_zCC) | (1 << xP1_yP1_zP1) |
				(1 << xP1_yCC_zM1) | (1 << xP1_yCC_zCC) | (1 << xP1_yCC_zP1),
			// Front face
			(1 << xM1_yM1_zM1) | (1 << xCC_yM1_zM1) | (1 << xP1_yM1_zM1) |
				(1 << xM1_yCC_zM1) | (1 << xCC_yCC_zM1) | (1 << xP1_yCC_zM1) |
				(1 << xM1_yP1_zM1) | (1 << xCC_yP1_zM1) | (1 << xP1_yP1_zM1),
			// Back face
			(1 << xM1_yM1_zP1) | (1 << xCC_yM1_zP1) | (1 << xP1_yM1_zP1) |
				(1 << xM1_yCC_zP1) | (1 << xCC_yCC_zP1) | (1 << xP1_yCC_zP1) |
				(1 << xM1_yP1_zP1) | (1 << xCC_yP1_zP1) | (1 << xP1_yP1_zP1),
			// Bottom face
			(1 << xM1_yM1_zM1) | (1 << xM1_yM1_zCC) | (1 << xM1_yM1_zP1) |
				(1 << xCC_yM1_zM1) | (1 << xCC_yM1_zCC) | (1 << xCC_yM1_zP1) |
				(1 << xP1_yM1_zM1) | (1 << xP1_yM1_zCC) | (1 << xP1_yM1_zP1),
			// Top face
			(1 << xM1_yP1_zM1) | (1 << xM1_yP1_zCC) | (1 << xM1_yP1_zP1) |
				(1 << xCC_yP1_zM1) | (1 << xCC_yP1_zCC) | (1 << xCC_yP1_zP1) |
				(1 << xP1_yP1_zM1) | (1 << xP1_yP1_zCC) | (1 << xP1_yP1_zP1),
		};
		
		#endregion
	}
}