using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Remoting.Messaging;
using System.Security.AccessControl;
using System.Security.Policy;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using VoxelIsometricRenderer.Properties;

namespace VoxelIsometricRenderer
{
    public enum LightDirection
    {
        North,
        NorthEast,
        NorthWest,
        East,
        West,
        SouthEast,
        SouthWest,
        South
    }
    public enum LightShape
    {
        Cylinder,
        Sphere,
        Cube,
        Cuboid
    }
    public class LightSource
    {
        int RangeX;
        int RangeY;
        int RangeZ;
        int XWorldPos;
        int YWorldPos;
        int ZWorldPos;
        LightShape Shape;
        byte[,,] LightLevels;
        float[] LightLevelModifier = new float[] { 1.0f, 1.0f, 1.0f };
        int Brightness = 64;
        public void SetPosition(int[] pos)
        {
            XWorldPos = pos[0];
            YWorldPos = pos[1];
            ZWorldPos = pos[2];
        }
        public int[] GetPosition()
        {
            return new int[] { XWorldPos, YWorldPos, ZWorldPos };
        }
        public int[] GetRadius()
        {
            return new int[] {RangeX,RangeY, RangeZ };
        }
        public int GetAverageRadius()
        {
            return (RangeX + RangeY + RangeZ) / 3;
        }
        public float[] GetRGBLightLevels(int level)
        {
            float Multiplier = (float)level / 255.0f;
            return new float[] { LightLevelModifier[0] * Multiplier, LightLevelModifier[1] * Multiplier, LightLevelModifier[2] * Multiplier };
        }
        public int GetLightLevel(int RelativeBlockX, int RelativeBlockY, int RelativeBlockZ, int face, bool BlocksLight)
        {
            int CentreX = LightLevels.GetLength(0) / 2;
            int CentreY = LightLevels.GetLength(1) / 2;
            int CentreZ = LightLevels.GetLength(2) / 2;
            int LightLevel = 0;

            if(RelativeBlockX >= 0 && RelativeBlockX < LightLevels.GetLength(0) &&
               RelativeBlockY >= 0 && RelativeBlockY < LightLevels.GetLength(1) &&
               RelativeBlockZ >= 0 && RelativeBlockZ < LightLevels.GetLength(2))
            {
                LightLevel = LightLevels[RelativeBlockX,RelativeBlockY,RelativeBlockZ];
                float DistanceX = RelativeBlockX - CentreX;
                float DistanceZ = RelativeBlockZ - CentreZ;
                double CorrectLightLevel =Math.Atan2(RelativeBlockX - CentreX, RelativeBlockZ - CentreZ);
                int DirectionX = 0;
                if (BlocksLight)
                {
                    switch (face)
                    {
                        case 0:
                            CorrectLightLevel += (Math.PI * 0 / 180);
                            if (CentreZ > RelativeBlockZ)
                            {
                                DirectionX = 180;
                            }
                            if (CentreZ == RelativeBlockZ)
                            {
                                DirectionX = 90;
                            }
                            if (CentreZ < RelativeBlockZ)
                            {
                                DirectionX = 0;
                            }
                            break;
                        case 1:
                            CorrectLightLevel += (Math.PI * 180 / 180);
                            if (CentreZ < RelativeBlockZ)
                            {
                                DirectionX = 180;
                            }
                            if (CentreZ == RelativeBlockZ)
                            {
                                DirectionX = 90;
                            }
                            if (CentreZ > RelativeBlockZ)
                            {
                                DirectionX = 0;
                            }
                            break;
                        case 2:
                            CorrectLightLevel += (Math.PI * 90 / 180);
                            if (CentreX < RelativeBlockX)
                            {
                                DirectionX = 180;
                            }
                            if (CentreX == RelativeBlockX)
                            {
                                DirectionX = 90;
                            }
                            if (CentreX > RelativeBlockX)
                            {
                                DirectionX = 0;
                            }
                            break;
                        case 3:
                            CorrectLightLevel += (Math.PI * 270 / 180);
                            if (CentreX > RelativeBlockX)
                            {
                                DirectionX = 180;
                            }
                            if (CentreX == RelativeBlockX)
                            {
                                DirectionX = 90;
                            }
                            if (CentreX < RelativeBlockX)
                            {
                                DirectionX = 0;
                            }
                            break;
                        case 4:
                            if (CentreY < RelativeBlockY)
                            {
                                DirectionX = 180;
                            }
                            if(CentreY == RelativeBlockY)
                            {
                                DirectionX = 90;
                            }
                            if(CentreY > RelativeBlockY)
                            {
                                DirectionX = 0;
                            }
                            break;
                        case 5:
                            if (CentreY > RelativeBlockY)
                            {
                                DirectionX = 180;
                            }
                            if (CentreY == RelativeBlockY)
                            {
                                DirectionX = 90;
                            }
                            if (CentreY < RelativeBlockY)
                            {
                                DirectionX = 0;
                            }
                            break;
                    }
                    /* if (CentreZ < RelativeBlockZ && RelativeBlockZ > RelativeBlockX)
                     {
                         for (int i = RelativeBlockZ; i < LightLevels.GetLength(2); i++)
                         {
                             LightLevels[RelativeBlockX, RelativeBlockY, i] = 0;
                         }
                     } else if (CentreZ > RelativeBlockZ && RelativeBlockZ < RelativeBlockX)
                     {
                         for (int i = RelativeBlockZ; i >= 0; i--)
                         {
                             LightLevels[RelativeBlockX, RelativeBlockY, i] = 0;
                         }
                     } else
                     {
                         if (CentreX < RelativeBlockX && RelativeBlockX > RelativeBlockZ)
                         {
                             for (int i = RelativeBlockX; i < LightLevels.GetLength(0); i++)
                             {
                                 LightLevels[i, RelativeBlockY, RelativeBlockZ] = 0;
                             }
                         }
                         else if (CentreX > RelativeBlockX && RelativeBlockX < RelativeBlockZ)
                         {
                             for (int i = RelativeBlockZ; i >= 0; i--)
                             {
                                 LightLevels[i, RelativeBlockY, RelativeBlockZ] = 0;
                             }
                         }
                     }*/
                    LightLevel = face > 3? (int)(LightLevel * ((1 + Math.Cos(Math.PI * DirectionX / 180)) / 2)) : (int)(LightLevel * ((1 + Math.Cos(CorrectLightLevel)) / 2));
                    //LightLevel = (int)(LightLevel * ((1 + Math.Cos(Math.PI * DirectionX / 180)) / 2));
                    // MessageBox.Show(LightLevel + "");
                }
            }
            return LightLevel > 0 ? LightLevel : 1;
        }
        public static bool IsWithinCircle(int Radius, int CentreX, int CentreY, int posX, int PosY)
        {
            return LightingManager.Square(posX - CentreX) + LightingManager.Square(PosY - CentreY) < LightingManager.Square(Radius);
        }
        public static bool IsWithinSphere(int Radius, int CentreX, int CentreY, int CentreZ, int posX, int PosY, int PosZ)
        {
            return LightingManager.Square(posX - CentreX) + LightingManager.Square(PosY - CentreY) + LightingManager.Square(PosZ - CentreZ) < LightingManager.Square(Radius);
        }
        public LightSource(LightShape shape, int RangeX, int RangeY, int RangeZ, int XWorldPos, int YWorldPos, int ZWorldPos,int Brightness)
        {
            this.Shape = shape;
            this.RangeX = RangeX;
            this.RangeY = RangeY;
            this.RangeZ = RangeZ;
            this.XWorldPos = XWorldPos;
            this.YWorldPos = YWorldPos;
            this.ZWorldPos = ZWorldPos;
            this.Brightness = Brightness;
            SetUpLightLevels();
        }
        public byte CalculateLightLevel(int CentreX, int CentreY, int CentreZ, int PosX, int PosY, int PosZ, int RangeX, int RangeY, int RangeZ, int MaxLevel)
        {
            int[] Distances = new int[] { CentreX - PosX, CentreY - PosY, CentreZ - PosZ};
            int[] Centres = new int[] { CentreX, CentreY, CentreZ };
            float[] LightLevel = new float[] {(255.0f/ RangeX) * (MaxLevel / 255.0f), (255.0f/ RangeY) * (MaxLevel / 255.0f), (255.0f/ RangeZ) * (MaxLevel / 255.0f) };
            float TotalLightLevel = 0;
            int Dimensions = 0;
            for (int i = 0; i < 3; i++)
            {
                    Distances[i] = Distances[i] > 0 ? Distances[i] : -Distances[i];
            }
            //if (PosY < CentreY && PosX == CentreX && PosZ == CentreZ) { MessageBox.Show(Distances[1] + ""); }
            //if (RangeX >= Distances[0] && PosY == CentreX && PosZ == CentreZ) MessageBox.Show(String.Format("LightLevel: {0}\nRange:{1}\nDistance:{2}\nRange/Distance:{3}", LightLevel[0], RangeX, Distances[0], RangeX / (float)Distances[0]));
            LightLevel = new float[] { RangeX >= Distances[0] ? LightLevel[0] * (RangeX / ((float)Distances[0] > 0 ? (float)Distances[0] : 1)) : 0, RangeY >= Distances[1] ? LightLevel[1] * (RangeY / ((float)Distances[1] > 0 ? (float)Distances[1] : 1)) : 0, RangeZ >= Distances[2] ? LightLevel[2] * (RangeZ / ((float)Distances[2] > 0 ? (float)Distances[2] : 1)) : 0 };
            for (int i = 0; i < 3; i++)
            {
                if (Centres[i] >= 0)
                {
                    Dimensions++;
                    TotalLightLevel += LightLevel[i];
                }
            }
            TotalLightLevel /= Dimensions;
            return (byte)Math.Min(Math.Max(TotalLightLevel,1),255);
        }
        private void SetUpLightLevels()
        {
            LightLevels = new byte[RangeX*2, RangeY*2, RangeZ*2];
            int CentreX = LightLevels.GetLength(0) / 2;
            int CentreY = LightLevels.GetLength(1) / 2;
            int CentreZ = LightLevels.GetLength(2) / 2;
            for (int x = 0; x < LightLevels.GetLength(0); x++)
            {
                for (int y = 0; y < LightLevels.GetLength(1); y++)
                {
                    for (int z = 0; z < LightLevels.GetLength(2); z++)
                    {
                        switch (Shape)
                        {
                            case LightShape.Sphere:
                                if (IsWithinSphere((RangeX + RangeY + RangeZ)/3, CentreX, CentreY, CentreZ, x, y, z)){
                                    LightLevels[x, y, z] = CalculateLightLevel(CentreX, CentreY, CentreZ, x, y, z, RangeX, RangeY, RangeZ,Brightness);
                                }
                                break;
                            case LightShape.Cylinder:
                                if (IsWithinCircle((RangeX + RangeY) / 2, CentreX, CentreZ, x, z))
                                {
                                    LightLevels[x, y, z] = CalculateLightLevel(CentreX, -1, CentreZ, x, y, z, RangeX, RangeY, RangeZ, Brightness);
                                }
                                break;
                            default:
                                    LightLevels[x, y, z] = CalculateLightLevel(CentreX, CentreY, CentreZ, x, y, z, RangeX, RangeY, RangeZ, Brightness);
                                break;
                        }
                    }
                }

            }
        }
        public LightSource(LightShape shape, int Range, int XWorldPos, int YWorldPos, int ZWorldPos,int Brightness)
        {
            this.Shape = shape;
            this.RangeX = Range;
            this.RangeY = Range;
            this.RangeZ = Range;
            this.XWorldPos = XWorldPos;
            this.YWorldPos = YWorldPos;
            this.ZWorldPos = ZWorldPos;
            this.Brightness = Brightness;
            SetUpLightLevels();
        }
        public LightSource(LightShape shape, int Range, int XWorldPos, int YWorldPos, int ZWorldPos, float[] RGBLightLevels, int Brightness)
        {
            this.Shape = shape;
            this.RangeX = Range;
            this.RangeY = Range;
            this.RangeZ = Range;
            this.XWorldPos = XWorldPos;
            this.YWorldPos = YWorldPos;
            this.ZWorldPos = ZWorldPos;
            this.LightLevelModifier = RGBLightLevels;
            this.Brightness = Brightness;
            SetUpLightLevels();
        }
        public LightSource( LightShape shape, int RangeX, int RangeY, int RangeZ, int XWorldPos, int YWorldPos, int ZWorldPos, float[] RGBLightLevels, int Brightness)
        {
            this.Shape = shape;
            this.RangeX = RangeX;
            this.RangeY = RangeY;
            this.RangeZ = RangeZ;
            this.XWorldPos = XWorldPos;
            this.YWorldPos = YWorldPos;
            this.ZWorldPos = ZWorldPos;
            this.LightLevelModifier = RGBLightLevels;
            this.Brightness = Brightness;
            SetUpLightLevels();
        }
    }
    public static class LightingManager
    {
        public static float[] AmbientColouring = new float[] { 1.0f, 1.0f, 1.0f };
        public static float[] AmbientMinimums = new float[] { 0.05f, 0.05f, 0.05f, 0.05f, 0.05f, 0.05f };
        public static float[] DefaultValues = new float[] { 0.6f, 0.6f, 0.6f, 0.6f, 1.0f, 0.4f };
        public static float[] SkyLightModifiedValues = new float[] { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
        public static float[] SunLightColourModifier = new float[] { 1.1f, 1.0f, 0.8f };
        public static float[] MoonLightColourModifier = new float[] { 0.5f, 0.6f, 0.85f };
        public static float[] sunAngles = new float[] { 135, 0 };
        public static float[] SkyLightMetrics = new float[] { 0, 0, 0, 0,0,0 };
        public static List<LightSource> Lights = new List<LightSource>();
        public static LightDirection SunAngle = LightDirection.North;
        public static float MaxBloom = 1.05f;
        public static float[] GetLightsOnCube(int WorldX, int WorldY, int WorldZ, int face, bool BlocksLight)
        {
            float[] RGBLevels = new float[] { 0, 0, 0 };
            int LightCount = 0;
            Lights.ForEach(Light =>
            {
                int DistX = WorldX - (Light.GetPosition()[0] - Light.GetRadius()[0]);
                int DistY = WorldY - (Light.GetPosition()[1] - Light.GetRadius()[1]);
                int DistZ = WorldZ - (Light.GetPosition()[2] - Light.GetRadius()[2]);
                if (DistX >= 0 && DistY >= 0 && DistZ >= 0 && DistX - Light.GetRadius()[0] <= 0 && DistY - Light.GetRadius()[1] <= 0 && DistZ - Light.GetRadius()[2] <= 0)
                {
                    //MessageBox.Show("X: " + DistX + " Y: " + DistY + " Z: " + DistZ);
                    float[] LightRGB = new float[3];
                    LightCount++;
                    int LightLevel = Light.GetLightLevel(DistX, DistY, DistZ, face, BlocksLight);
                    LightRGB = Light.GetRGBLightLevels(LightLevel);
                    RGBLevels[0] += LightRGB[0];
                    RGBLevels[1] += LightRGB[1];
                    RGBLevels[2] += LightRGB[2];

                }
            });
            return new float[] { RGBLevels[0] != 0 ? RGBLevels[0]/LightCount : 0, RGBLevels[1] != 0 ? RGBLevels[1]/LightCount : 0, RGBLevels[2] != 0 ? RGBLevels[2]/LightCount : 0 };
            }
        public static void UpdateLighting()
        {
            float[] AngleRanges = new float[] { 0, 45, 90, 135, 180, 225, 270, 315 };
            float[] AngleOffsets = new float[] { 0, 180, 90, 270, -1, -1 };
            LightDirection[] directions = new LightDirection[] { LightDirection.North, LightDirection.NorthEast, LightDirection.East, LightDirection.SouthEast, LightDirection.South, LightDirection.SouthWest, LightDirection.West, LightDirection.NorthWest };
            if (sunAngles[0] > 360) sunAngles[0] -= 360;
            if (sunAngles[1] > 360) sunAngles[1] -= 360;
            if (sunAngles[0] <= -360) sunAngles[0] += 360;
            if (sunAngles[1] <= -360) sunAngles[1] += 360; 
            float CurrentAngle = 0;
            int AngleIndex = 0;
            float MoonAngle = (float)((1 + Math.Sin(Math.PI * (sunAngles[1] - 90) / 180)) / 2.0);
            float SunLightAngle = (float)((1 + Math.Cos(Math.PI * sunAngles[1] / 180)) / 2.0); 
            for (int i = 0; i < AngleRanges.Length; i++)
            {
                if (sunAngles[0] < AngleRanges[i])
                {
                    CurrentAngle = AngleRanges[i];
                    AngleIndex = i;
                    break;
                }
            }
            if (Square(CurrentAngle - sunAngles[0]) > Square(sunAngles[0] - AngleRanges[AngleIndex - 1 >= 0 ? AngleIndex - 1 : AngleRanges.Length - 1]))
            {
                AngleIndex = AngleIndex - 1 >= 0 ? AngleIndex - 1 : AngleRanges.Length - 1;
            }
            SunAngle = directions[AngleIndex];
            for (int i = 0; i < SkyLightModifiedValues.Length - 2; i++)
            {
                SkyLightModifiedValues[i] = (float)((1 + Math.Cos(Math.PI * (sunAngles[0] + AngleOffsets[i]) / 180) / 2.0) * (float)Math.Min(Math.Max((1 + Math.Cos(Math.PI * (sunAngles[1] + 45) / 180)) / 2.0, MoonAngle/2),0.9));
            }
            for (int i = SkyLightModifiedValues.Length - 2; i < SkyLightModifiedValues.Length; i++)
            {
                SkyLightModifiedValues[i] = (float)Math.Min(SunLightAngle + (MoonAngle/ 2),MaxBloom);
            }
            for (int i = 0; i < AmbientColouring.Length; i++)
            {
                AmbientColouring[i] = SunLightColourModifier[i] * SunLightAngle + MoonLightColourModifier[i] * MoonAngle;
                SkyLightMetrics[3 + i] = AmbientColouring[i];
            }
            AmbientColouring[2] -= sunAngles[1] > 180 || sunAngles[1] < 90 ? Square(Square((float)(Math.Max(Math.Cos(Math.PI * (sunAngles[1] + 45) / 180), 0)))) / 2.0f : 0;//(float)(Math.Max(Math.Cos(Math.PI * (sunAngles[1] + 45) / 180),0));
            AmbientColouring[1] -= sunAngles[1] > 180 || sunAngles[1] < 90 ? Square(Square((float)(Math.Max(Math.Cos(Math.PI * (sunAngles[1] + 45) / 180), 0))) / 3.0f) : 0;// (float)(Math.Max(Math.Cos(Math.PI * (sunAngles[1] + 45) / 180),0))/2.0f;
            SkyLightMetrics[0] = (float)SunLightAngle + (float)(MoonAngle / 5);
            SkyLightMetrics[2] = MoonAngle;
        }
        public static float Square(float ValueIn)
        {
            return ValueIn * ValueIn;
        }
    }
    public static class TextureManager
    {
        public static List<Bitmap> textures = new List<Bitmap>() {Resources._16xDefaultTile};
        public static List<Bitmap> PreCompiledModels = new List<Bitmap>();
        public static Bitmap[,] GeneratedFaceTextures = new Bitmap[0,0];
        public static int VoxelCount = 0;
        public static int VoxelSize = 32;
        public static float RenderScale = 2.0f;
        public static Bitmap GetModel(int modelNum)
        {
            return PreCompiledModels[modelNum];
        }
        public static void CreateModel(Voxel voxel)
        {
            if (GeneratedFaceTextures.GetLength(1) < 6) GenerateVoxelFaces();
            int ImageWidth = 0;
            int ImageHeight = 0;
            int[] faces = voxel.getFaces();
            int[] DrawOrder = voxel.GetDrawOrder();
            for(int i = 0; i < faces.Length; i++)
            {
                if (faces[i] >= 0 && GeneratedFaceTextures[faces[i], i].Width > ImageWidth) ImageWidth = GeneratedFaceTextures[faces[i], i].Width;
                if (faces[i] >= 0 && GeneratedFaceTextures[faces[i], i].Height > ImageHeight) ImageHeight = GeneratedFaceTextures[faces[i], i].Height;
            }
            Bitmap GeneratedModel = new Bitmap(ImageWidth, ImageHeight);
            voxel.Draw(Graphics.FromImage(GeneratedModel),0,0,0,0,0,0,false);
            voxel.SetPreCompiledModel(PreCompiledModels.Count);
            PreCompiledModels.Add(GeneratedModel);
            GeneratedModel.Save("Model" + voxel.GetModelNum() + ".png");
        }
        public static float GetSunlightValue(int face, int ShadowLevel)
        {
            return (LightingManager.SkyLightModifiedValues[face] * LightingManager.DefaultValues[face]) / (float)ShadowLevel;
        }
        public static float GetShadingValue(float LightValue, int RGBIndex, int FaceIndex)
        {
            return GetShadingValue(LightValue, RGBIndex, FaceIndex, new float[] { 0, 0, 0 });
        }
        public static float GetShadingValue(float LightValue, int RGBIndex, int FaceIndex, float[] Lights)
        {
            return ((float)Math.Max(LightValue * LightingManager.AmbientColouring[RGBIndex], LightingManager.AmbientMinimums[FaceIndex])) + Lights[RGBIndex];
        }
        public static ImageAttributes GetShadow(int face) { return GetShadow(face, 0, 0,0,0, false); }
        public static ImageAttributes GetShadow(int face, int WorldX, int WorldY, int WorldZ, int ShadowLevel, bool BlocksLight)
        {
            ImageAttributes imageAttributes;
            float[][] colorMatrixElements;
            float[] Lights = LightingManager.GetLightsOnCube(WorldX, WorldY, WorldZ, face, BlocksLight);
            ColorMatrix colorMatrix;
            switch (face) {
                case 0:
                     colorMatrixElements = new float[][]{
                     new float[] { GetShadingValue(GetSunlightValue(face,ShadowLevel), 0,face,Lights),  0,  0,  0, 0},        // red 
                     new float[] {0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 1, face, Lights),  0,  0, 0},        // green 
                    new float[] {0,  0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 2, face, Lights),  0, 0},        // blue 
                    new float[] {0,  0,  0,  1, 0},        // alpha 
                    new float[] {0, 0, 0, 0, 1}};  

                    colorMatrix = new ColorMatrix(colorMatrixElements);
                    imageAttributes = new ImageAttributes();
                    imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
                    return imageAttributes;
                case 1:
                     colorMatrixElements = new float[][]{
                     new float[] { GetShadingValue(GetSunlightValue(face,ShadowLevel), 0,face, Lights),  0,  0,  0, 0},        // red 
                     new float[] {0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 1, face, Lights),  0,  0, 0},        // green 
                    new float[] {0,  0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 2, face, Lights),  0, 0},      // blue 
                    new float[] {0,  0,  0,  1, 0},        // alpha 
                    new float[] {0, 0, 0, 0, 1}}; 

                    colorMatrix = new ColorMatrix(colorMatrixElements);
                    imageAttributes = new ImageAttributes();
                    imageAttributes.SetColorMatrix( colorMatrix,ColorMatrixFlag.Default,ColorAdjustType.Bitmap);
                    return imageAttributes;
                case 2:
                    colorMatrixElements = new float[][]{
                     new float[] { GetShadingValue(GetSunlightValue(face,ShadowLevel), 0,face, Lights),  0,  0,  0, 0},        // red 
                     new float[] {0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 1, face, Lights),  0,  0, 0},        // green 
                    new float[] {0,  0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 2, face, Lights),  0, 0},        // blue 
                    new float[] {0,  0,  0,  1, 0},        // alpha 
                    new float[] {0, 0, 0, 0, 1}};  

                    colorMatrix = new ColorMatrix(colorMatrixElements);
                    imageAttributes = new ImageAttributes();
                    imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
                    return imageAttributes;
                case 3:
                    colorMatrixElements = new float[][]{
                     new float[] { GetShadingValue(GetSunlightValue(face,ShadowLevel), 0,face, Lights),  0,  0,  0, 0},        // red 
                     new float[] {0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 1, face, Lights),  0,  0, 0},        // green 
                    new float[] {0,  0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 2, face, Lights),  0, 0},         // blue 
                    new float[] {0,  0,  0,  1, 0},        // alpha 
                    new float[] {0, 0, 0, 0, 1}};

                     colorMatrix = new ColorMatrix(colorMatrixElements);
                    imageAttributes = new ImageAttributes();
                    imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
                    return imageAttributes;
                case 4:
                    colorMatrixElements = new float[][]{
                     new float[] { GetShadingValue(GetSunlightValue(face,ShadowLevel), 0,face, Lights),  0,  0,  0, 0},        // red 
                     new float[] {0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 1, face, Lights),  0,  0, 0},        // green 
                    new float[] {0,  0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 2, face, Lights),  0, 0},        // blue 
                    new float[] {0,  0,  0,  1, 0},        // alpha 
                    new float[] {0, 0, 0, 0, 1}}; 

                    colorMatrix = new ColorMatrix(colorMatrixElements);
                    imageAttributes = new ImageAttributes();
                    imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
                    return imageAttributes;
                case 5:
                     colorMatrixElements = new float[][]{
                     new float[] { GetShadingValue(GetSunlightValue(face,ShadowLevel), 0,face, Lights),  0,  0,  0, 0},        // red 
                     new float[] {0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 1, face, Lights),  0,  0, 0},        // green 
                    new float[] {0,  0, GetShadingValue(GetSunlightValue(face,ShadowLevel), 2, face, Lights),  0, 0},      // blue 
                    new float[] {0,  0,  0,  1, 0},        // alpha 
                    new float[] {0, 0, -0, 0, 1}};   

                    colorMatrix = new ColorMatrix(colorMatrixElements);
                    imageAttributes = new ImageAttributes();
                    imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
                    return imageAttributes;
                default:
                    imageAttributes = new ImageAttributes();
                    return imageAttributes;

            }
        }
        public static Point IntegerCalculateIsometricProjection(int width, int height, int X, int Y, int Z, int Corner)
        {
            switch (Corner)
            {
                case 0:
                    return new Point(width / 2 + (int)((X * 1) + (Z * -1)),
                    height / 2 + (int)Math.Ceiling((X * 0.5f) + (Z * 0.5f) - (Y / 3) * 2));
                case 1:
                    return new Point(width / 2 + (int)((X * 1) + (Z * -1)),
                    height / 2 + (int)Math.Floor((X * 0.5f) + (Z * 0.5f) - (Y / 3) * 2));
                case 2:
                    return new Point(-1 + width / 2 + (int)((X * 1) + (Z * -1)),
                    height / 2 + (int)Math.Floor((X * 0.5f) + (Z * 0.5f) - (Y / 3) * 2));
                default:
                    return new Point(-1 + width / 2 + (int)((X * 1) + (Z * -1)),
                    height / 2 + (int)Math.Ceiling((X * 0.5f) + (Z * 0.5f) - (Y / 3) * 2));
            }
        }
        public static Point IntegerCalculateIsometricProjection(int width, int height, int X, int Y, int Z)
        {
            return new Point(width / 2 + (int)((X * 1) + (Z * -1)),
            height / 2 + (int)((X * 0.5f) + (Z * 0.5f) - (Y / 3) * 2));
        }
        public static PointF CalculateIsometricProjection(double[] Position, int width, int height, int X, int Y, int Z)
        {
            return new PointF(width / 2 + (float)(((/*Globals.CameraX*/ +X + Position[0]) * 0.5) + ((/*Globals.CameraZ +*/ Z + Position[1]) * -0.5)),
            height / 2 + (float)(((/*Globals.CameraX*/ +X + Position[0]) * 0.25f) + ((/*Globals.CameraZ*/ +Z + Position[1]) * 0.25f) - (((/*Globals.CameraY*/ +Position[2]) + (Y / 3) * 2))));
        }
        public static Bitmap GetVoxelFaceBitmap(int index, int face)
        {
            if (GeneratedFaceTextures.GetLength(1) < 6) GenerateVoxelFaces();
            return GeneratedFaceTextures[index,face];
        }
        public static Bitmap GetBitmap(int index)
        {
            return textures[index];
        }
        public static void AddBitmap(Bitmap texture)
        {
            textures.Add(texture);
        }
        public static void GenerateVoxelFaces()
        {
            GeneratedFaceTextures = new Bitmap[textures.Count, 6];
            for (int i = 0; i < textures.Count; i++)
            {
                for (int face = 0; face < 6; face++)
                {
                    Bitmap SkewedTexture = new Bitmap(textures[i].Width * 2, textures[i].Height * 2);
                    int y = 0;// textures[i].Height;
                    switch (face)
                    {
                        case 0:
                            y = 1;
                            for(int x = 0; x < textures[i].Width; x++)
                            {
                                for(int ypx = 0; ypx < textures[i].Height; ypx++)
                                {
                                    SkewedTexture.SetPixel(textures[i].Width+ x, y + ypx, textures[i].GetPixel(x, ypx));
                                }
                                if (x % 2 != 0) y++;
                            }
                            y = 0;
                            break;
                        case 1:
                            for (int x = textures[i].Width - 1; x >= 0 ; x--)
                            {
                                for (int ypx = 0; ypx < textures[i].Height; ypx++)
                                {
                                    SkewedTexture.SetPixel(x, SkewedTexture.Height - (y + ypx + 1), textures[i].GetPixel(x, textures[i].Height - 1 - ypx));
                                }
                                if (x % 2 == 0) y++;
                            }
                            y = 0;
                            break;
                        case 2:
                            for (int x = 0; x < textures[i].Width; x++)
                            {
                                for (int ypx = 0; ypx < textures[i].Height; ypx++)
                                {
                                    SkewedTexture.SetPixel(textures[i].Width + x, SkewedTexture.Height - (y + ypx + 1), textures[i].GetPixel(x, textures[i].Height - 1 - ypx));
                                }
                                if (x % 2 != 0) y++;
                            }
                            y = 0;
                            break;
                        case 3:
                            y = 1;
                            for (int x = textures[i].Width - 1; x >= 0; x--)
                            {
                                for (int ypx = 0; ypx < textures[i].Height; ypx++)
                                {
                                    SkewedTexture.SetPixel(x, y + ypx, textures[i].GetPixel(x, ypx));
                                }
                                if (x % 2 == 0) y++;
                            }
                            y = 0;
                            break;
                        case 4:
                            Bitmap[] Textures = new Bitmap[4];
                            Bitmap MergedTextures = new Bitmap(SkewedTexture.Width, SkewedTexture.Height);
                            for (int l = 0; l < 4; l++)
                            {
                                Textures[l] = new Bitmap(SkewedTexture.Width, SkewedTexture.Height);
                            }
                            for(int l = 0; l < 4; l++)
                            {
                                for (int x = 0; x < textures[i].Width; x++)
                                {
                                    for (int ypx = 0; ypx < textures[i].Height; ypx++)
                                    {
                                        Point PixelLocation = IntegerCalculateIsometricProjection(textures[i].Width * 2, 0, x, 0, ypx,l);
                                        PixelLocation.X = Math.Max(Math.Min(PixelLocation.X, SkewedTexture.Width - 1), 0);
                                        PixelLocation.Y = Math.Max(Math.Min(PixelLocation.Y, SkewedTexture.Height - 1), 0);
                                        Textures[l].SetPixel(PixelLocation.X, PixelLocation.Y, textures[i].GetPixel(x, ypx));
                                    }
                                }
                            }
                            for (int l = 0; l < 4; l++)
                            {
                                int Startx = 0;
                                int Starty = 0;
                                int endX = Textures[l].Width;
                                int endY = Textures[l].Height;
                                switch (l)
                                {
                                    case 1:
                                        Startx = Textures[l].Width / 2;
                                        Starty = 0;
                                        endX = Textures[l].Width;
                                        endY = Textures[l].Height/4;
                                        break;
                                    case 0:
                                        Startx = Textures[l].Width / 2;
                                        Starty = Textures[l].Height / 4;
                                        endX = Textures[l].Width;
                                        endY = Textures[l].Height/2;
                                        break;
                                    case 2:
                                        Startx = 0;
                                        Starty = 0;
                                        endX = Textures[l].Width/2;
                                        endY = Textures[l].Height / 4 ;
                                        break;
                                    case 3:
                                        Startx = 0;
                                        Starty = Textures[l].Height / 4;
                                        endX = Textures[l].Width/2;
                                        endY = Textures[l].Height/2;
                                        break;
                                }
                                for(int x = Startx; x < endX; x++)
                                {
                                    for (int ypx = Starty; ypx < endY;ypx++)
                                    {
                                        MergedTextures.SetPixel(x, ypx, Textures[l].GetPixel(x, ypx));
                                    }
                                }
                            }
                            SkewedTexture = MergedTextures;
                            y = 0;
                            break;
                        case 5:
                            Textures = new Bitmap[4];
                            MergedTextures = new Bitmap(SkewedTexture.Width, SkewedTexture.Height);
                            for (int l = 0; l < 4; l++)
                            {
                                Textures[l] = new Bitmap(SkewedTexture.Width, SkewedTexture.Height);
                            }
                            for (int l = 0; l < 4; l++)
                            {
                                for (int x = 0; x < textures[i].Width; x++)
                                {
                                    for (int ypx = 0; ypx < textures[i].Height; ypx++)
                                    {
                                        Point PixelLocation = IntegerCalculateIsometricProjection(textures[i].Width * 2, 0, x, 0, ypx, l);
                                        PixelLocation.X = Math.Max(Math.Min(PixelLocation.X, SkewedTexture.Width - 1), 0);
                                        PixelLocation.Y = Math.Max(Math.Min(textures[i].Height + PixelLocation.Y, SkewedTexture.Height - 1), 0);
                                        Textures[l].SetPixel(PixelLocation.X, PixelLocation.Y, textures[i].GetPixel(x, ypx));
                                    }
                                }
                            }
                            for (int l = 0; l < 4; l++)
                            {
                                int Startx = 0;
                                int Starty = 0;
                                int endX = Textures[l].Width;
                                int endY = Textures[l].Height;
                                switch (l)
                                {
                                    case 1:
                                        Startx = Textures[l].Width / 2;
                                        Starty = Textures[l].Height / 2;
                                        endX = Textures[l].Width;
                                        endY = (Textures[l].Height / 4) * 3;
                                        break;
                                    case 0:
                                        Startx = Textures[l].Width / 2;
                                        Starty = (Textures[l].Height / 4) * 3;
                                        endX = Textures[l].Width;
                                        endY = Textures[l].Height;
                                        break;
                                    case 2:
                                        Startx = 0;
                                        Starty = Textures[l].Height / 2;
                                        endX = Textures[l].Width / 2;
                                        endY = (Textures[l].Height / 4) * 3;
                                        break;
                                    case 3:
                                        Startx = 0;
                                        Starty = (Textures[l].Height / 4)*3;
                                        endX = Textures[l].Width / 2;
                                        endY = Textures[l].Height;
                                        break;
                                }
                                for (int x = Startx; x < endX; x++)
                                {
                                    for (int ypx = Starty; ypx < endY; ypx++)
                                    {
                                        MergedTextures.SetPixel(x, ypx, Textures[l].GetPixel(x, ypx));
                                    }
                                }
                            }
                            SkewedTexture = MergedTextures;
                            y = 0;
                            break;

                    }
                    GeneratedFaceTextures[i, face] = SkewedTexture;
                }
            }
        }
    }
    public class Voxel
    {
        int VoxelNum;
        int[] faces = new int[6];
        int[] DrawOrder = new int[] { 5, 0, 3, 1, 2, 4 };
        int ModelType = 0;
        int ModelNum = -1;
        bool Cull = false;
        public int[] getFaces()
        {
            return faces;
        }
        public int[] GetDrawOrder()
        {
            return DrawOrder;
        }
        public int GetModelNum()
        {
            return ModelNum;
        }
        public int GetModelType()
        {
            return ModelType;
        }
        public int GetVoxelNum()
        {
            return VoxelNum;
        }
        public Voxel(int ModelType,int NorthFace, int SouthFace, int EastFace, int WestFace, int UpFace, int DownFace, bool cull)
        {
            this.ModelType = ModelType;
            faces = new int[] { NorthFace, SouthFace, EastFace, WestFace, UpFace, DownFace };
            VoxelNum = TextureManager.VoxelCount;
            TextureManager.VoxelCount++;
            if (cull) SetCullFaces(false,true,false,false, false, false);
            Cull = cull;
        }
        public void SetCullFaces(bool up, bool down, bool West, bool East,bool north, bool south)
        {
             DrawOrder = new int[] { down?-1:5, north?-1:0, West?-1:3, south?-1:1, East?-1:2, up?-1:4 };
        }
        public void SetPreCompiledModel(int ModelNum)
        {
            this.ModelNum = ModelNum;
        }
        public Voxel(int NorthFace, int SouthFace, int EastFace, int WestFace, int UpFace, int DownFace)
        {
            faces = new int[] {NorthFace, SouthFace, EastFace, WestFace, UpFace, DownFace};
            VoxelNum = TextureManager.VoxelCount;
            TextureManager.VoxelCount++;
        }
        virtual public void Draw(Graphics g, int x, int y, int WorldX, int WorldY, int WorldZ, int ShadowLevel, bool BlocksLight)
        {
            for (int i = 0; i < DrawOrder.Length; i++) {
                if (DrawOrder[i] > 0 && DrawOrder[i] < 6 && faces[DrawOrder[i]] >= 0)
                {
                    if (ModelNum == -1)
                    {
                        Bitmap Side;
                        switch (ModelType)
                        {
                            case 0:
                                Side = GetRenderedSide(DrawOrder[i]);
                                g.DrawImage(Side, new Rectangle(x, y, Side.Width, Side.Height), 0, 0, Side.Width, Side.Height, GraphicsUnit.Pixel, TextureManager.GetShadow(DrawOrder[i], WorldX, WorldY,WorldZ, ShadowLevel, BlocksLight));
                                break;
                            default:
                                
                                switch (DrawOrder[i])
                                {
                                    case 0:
                                        Side = GetRenderedSide(0);
                                        g.DrawImage(Side, new Rectangle(x - (Side.Width / 4), y, Side.Width, Side.Height),0,0,Side.Width,Side.Height, GraphicsUnit.Pixel, TextureManager.GetShadow(0, WorldX, WorldY, WorldZ, ShadowLevel, BlocksLight));
                                    break;
                                    case 1:
                                        Side = GetRenderedSide(1);
                                        g.DrawImage(Side, new Rectangle(x + (Side.Width / 4), y - (Side.Height / 4), Side.Width, Side.Height),0,0,Side.Width,Side.Height, GraphicsUnit.Pixel, TextureManager.GetShadow(1, WorldX, WorldY, WorldZ, ShadowLevel, BlocksLight));
                                        break;
                                    case 2:
                                        Side = GetRenderedSide(2);
                                        g.DrawImage(Side, new Rectangle(x - (Side.Width / 4), y - (Side.Height / 4), Side.Width, Side.Height),0,0,Side.Width,Side.Height, GraphicsUnit.Pixel, TextureManager.GetShadow(2, WorldX, WorldY, WorldZ, ShadowLevel, BlocksLight));
                                        break;
                                    case 3:
                                        Side = GetRenderedSide(3);
                                        g.DrawImage(Side, new Rectangle( x + (Side.Width / 4),y,Side.Width,Side.Height),0,0,Side.Width,Side.Height, GraphicsUnit.Pixel, TextureManager.GetShadow(3, WorldX, WorldY, WorldZ, ShadowLevel, BlocksLight));
                                        break;
                                    default:
                                        Side = GetRenderedSide(DrawOrder[i]);
                                        g.DrawImage(Side, new Rectangle(x, y, Side.Width, Side.Height), 0, 0, Side.Width, Side.Height, GraphicsUnit.Pixel, TextureManager.GetShadow(DrawOrder[i], WorldX, WorldY, WorldZ, ShadowLevel, BlocksLight));
                                        break;
                                }
                                break;
                        }
                    }
                    else
                    {
                        g.DrawImage(TextureManager.GetModel(ModelNum), x, y);
                    }
                }
            }
        }
        public Bitmap GetRenderedSide(int FaceIndex)
        {
            return TextureManager.GetVoxelFaceBitmap(faces[FaceIndex], FaceIndex);
        }
        public int GetFace(int index)
        {
            return faces[index];
        }
        public int GetNorthFace()
        {
            return faces[0];
        }
        public int GetSouthFace()
        {
            return faces[1];
        }
        public int GetEastFace()
        {
            return faces[2];
        }
        public int GetWestFace()
        {
            return faces[3];
        }
        public int GetUpFace()
        {
            return faces[4];
        }
        public int GetDownFace()
        {
            return faces[5];
        }
        public void SetFace(int face, int Texture)
        {
             faces[face] = Texture;
        }
        public void SetNorthFace(int Texture)
        {
            faces[0] = Texture;
        }
        public void SetSouthFace(int Texture)
        {
             faces[1] = Texture;
        }
        public void SetEastFace(int Texture)
        {
             faces[2] = Texture;
        }
        public void SetWestFace(int Texture)
        {
             faces[3] = Texture;
        }
        public void SetUpFace(int Texture)
        {
             faces[4] = Texture;
        }
        public void SetDownFace(int Texture)
        {
             faces[5] = Texture;
        }
    }
    public static class VoxelRegistry
    {
        public static List<Voxel> Voxels = new List<Voxel>();
        public static int IndexVoxel(Voxel voxel)
        {
            if(Voxels.Contains(voxel)) return Voxels.IndexOf(voxel);
            Voxels.Add(voxel);
            return Voxels.IndexOf(Voxels.Last());
        }
        public static int VoxelExists(Voxel voxel)
        {
            return Voxels.Contains(voxel) ? Voxels.IndexOf(voxel) : -1;
        }
        public static Voxel FetchVoxel(int VoxelIndex)
        {
            return Voxels[VoxelIndex];
        }
    }
    public enum BlockType
    {
        Plant,
        Ground,
        Wood,
        Metal,
        BuildMaterial,
        Gas,
        Fluid
    }
    public class BlockState
    {
        bool TickUpdate = false;
        BlockType BlockType;
        bool BlocksLight = false;

        public BlockState(bool TickUpdate, BlockType blocktype, bool blocksLight)
        {
            this.TickUpdate = TickUpdate;
            this.BlockType = blocktype;
            BlocksLight = blocksLight;
        }
        public bool hasBlocksLight() { return BlocksLight; }
        public BlockType GetBlockType() { return BlockType; }
        public bool Ticks() {  return TickUpdate; }
    }
    public static class BlockstateRegistry
    {
        public static List<BlockState> Blockstates = new List<BlockState>();
        public static int GetBlockstateIndex(BlockState blockstate)
        {
            if (!Blockstates.Contains(blockstate)) Blockstates.Add(blockstate);
            return Blockstates.IndexOf(blockstate);
        }
        public static BlockState GetBlockstate(int index)
        {
            return Blockstates[index];
        }
        public static void SetBlockstate(int index, BlockState blockstate)
        {
            Blockstates[index] = blockstate;
        }
    }
    public class Chunk
    {
        public List<int>DrawList = new List<int>();
        public List<int[]>DrawPositions = new List<int[]>();
        public int[,,] BlockReg;
        public bool[,,] Culled;
        public int[] ChunkPos;
        public int ChunkID;
        public int[,,] SkyLightHeightMap;
        public Chunk(int X, int Y, int Z, int Width, int Height, int Length, int ChunkID)
        {
            this.ChunkID = ChunkID;
            BlockReg = new int[Width,Height,Length];
            Culled = new bool[Width,Height,Length];
            SkyLightHeightMap = new int[Width,Height,Length];
            ChunkPos = new int[] { X, Y, Z };
        }
        public void CalculateCull()
        {
            DrawList = new List<int>();
            int CulledCount = 0;
            for (int x = 0; x < Culled.GetLength(0); x++)
            {
                for (int y = 0; y < Culled.GetLength(1); y++)
                {
                    for (int z = 0; z < Culled.GetLength(2); z++)
                    {
                        if (!(x == Culled.GetLength(0) - 1 || x < Culled.GetLength(0) - 1 && (BlockReg[x + 1, y, z] != -1 && !BlockRegistry.FetchBlockState(BlockReg[x + 1, y, z]).hasBlocksLight() || BlockReg[x + 1, y, z] == -1))
                            && !(x > 0 && (BlockReg[x - 1, y, z] != -1 && !BlockRegistry.FetchBlockState(BlockReg[x - 1, y, z]).hasBlocksLight() || BlockReg[x - 1, y, z] == -1))
                            && !(y == Culled.GetLength(1) - 1 || y < Culled.GetLength(1) - 1 && (BlockReg[x, y + 1, z] != -1 && !BlockRegistry.FetchBlockState(BlockReg[x, y + 1, z]).hasBlocksLight() || BlockReg[x, y + 1, z] == -1))
                            && !(y > 0 && (BlockReg[x, y - 1, z] != -1 && !BlockRegistry.FetchBlockState(BlockReg[x, y - 1, z]).hasBlocksLight() || BlockReg[x, y - 1, z] == -1))
                            && !(z == Culled.GetLength(2) - 1 || z < Culled.GetLength(2) - 1 && (BlockReg[x, y, z + 1] != -1 && !BlockRegistry.FetchBlockState(BlockReg[x, y, z + 1]).hasBlocksLight() || BlockReg[x, y, z + 1] == -1))
                            && !(z > 0 && (BlockReg[x, y, z - 1] != -1 && !BlockRegistry.FetchBlockState(BlockReg[x, y, z - 1]).hasBlocksLight() || BlockReg[x, y, z - 1] == -1)))
                        {
                            Culled[x, y, z] = true;
                            CulledCount++;
                        }
                    }
                }
            }
            for (int x = 0; x < BlockReg.GetLength(0); x++)
            {
                for (int y = 0; y < BlockReg.GetLength(1); y++)
                {
                    for (int z = 0; z < BlockReg.GetLength(1); z++)
                    {
                        if (Culled[x,y,z] == false && BlockReg[x, y, z] != -1)
                        {
                            DrawList.Add(BlockReg[x,y,z]);
                            DrawPositions.Add(new int[] { x, y, z });
                        }
                    }
                }
            }

        }
        public void GenerateLightMap()
        {
            Chunk ChunkAbove = ChunkRegistry.FetchChunk(String.Format("x={0};y={1};z={2}", ChunkPos[0], ChunkPos[1] +1, ChunkPos[2]));
            for (int x = 0;x < SkyLightHeightMap.GetLength(0); x++)
            {
                for(int z = 0; z < SkyLightHeightMap.GetLength(1); z++)
                {
                    int Y = ChunkAbove != null ? ChunkAbove.SkyLightHeightMap[x,1,z] != 0 ? ChunkAbove.SkyLightHeightMap[x, 1, z] : 1 : 1;
                    for (int y = SkyLightHeightMap.GetLength(1) - 1; y > 0; y--)
                    {
                        SkyLightHeightMap[x, y, z] = Y;
                        if (BlockReg[x,y,z] != -1 && BlockRegistry.FetchBlockState(BlockReg[x, y, z]).hasBlocksLight())
                        {
                            Y++;
                        }
                    }
                }
            }
        }
        public void RenderBlocks(Graphics g)
        {
            if (DrawList.Count == 0)
            {
                for (int x = 0; x < BlockReg.GetLength(0); x++)
                {
                    for (int y = 0; y < BlockReg.GetLength(1); y++)
                    {
                        for (int z = 0; z < BlockReg.GetLength(1); z++)
                        {
                            if (BlockReg[x, y, z] != -1 && Culled[x, y, z] == false)
                            {
                                if (SkyLightHeightMap[x, y, z] == 0 ) SkyLightHeightMap[x, y, z] = 255;
                                BlockRegistry.RenderBlock(g, BlockReg[x, y, z], x + ChunkPos[0] * ChunkRegistry.FetchDimensions()[0], y + ChunkPos[1] * ChunkRegistry.FetchDimensions()[1], z + ChunkPos[2] * ChunkRegistry.FetchDimensions()[2], SkyLightHeightMap[x, y, z]);
                            }
                        }
                    }
                }
            }
            else
            {
                for (int i = 0; i < DrawList.Count; i++)
                {
                    if (SkyLightHeightMap[DrawPositions[i][0], DrawPositions[i][1], DrawPositions[i][2]] == 0 ) SkyLightHeightMap[DrawPositions[i][0], DrawPositions[i][1], DrawPositions[i][2]] = 255;
                    BlockRegistry.RenderBlock(g, DrawList[i], DrawPositions[i][0] + ChunkPos[0] * ChunkRegistry.FetchDimensions()[0], DrawPositions[i][1] + ChunkPos[1] * ChunkRegistry.FetchDimensions()[1], DrawPositions[i][2] + ChunkPos[2] * ChunkRegistry.FetchDimensions()[2], SkyLightHeightMap[DrawPositions[i][0], DrawPositions[i][1], DrawPositions[i][2]]);
                }
            }
        }
        public void CreateAir(int X, int Y, int Z)
        {
            BlockReg[X, Y, Z] = -1;
        }
        public void CreateBlock(int[] Faces, int RenderType, BlockState blockState, int X, int Y, int Z)
        {
            Voxel voxel = new Voxel(RenderType, Faces[0], Faces[1], Faces[2], Faces[3], Faces[4], Faces[5],blockState.hasBlocksLight());
            int index = VoxelRegistry.VoxelExists(voxel);
            if (index == -1)
            {
                index = VoxelRegistry.IndexVoxel(voxel);
            }
            Block newBlock = new Block(index, BlockstateRegistry.GetBlockstateIndex(blockState), ChunkID);
            int x = X + ChunkPos[0] * ChunkRegistry.FetchDimensions()[0];
            int y = Y + ChunkPos[1] * ChunkRegistry.FetchDimensions()[1];
            int z = Z + ChunkPos[2] * ChunkRegistry.FetchDimensions()[2];
            int BlockID = BlockRegistry.CheckInBlock(newBlock, x, y, z);
            BlockReg[X, Y, Z] = BlockID;
        }
        public Block FetchBlock(int x, int y, int z)
        {
            return BlockRegistry.FetchBlock(BlockReg[x,y,z]);
        }
        public void IndexBlock(int BlockIndex, int x, int y, int z)
        {
            BlockReg[x,y,z] = BlockIndex;
        }
        public string FetchPosString()
        {
            return String.Format("x={0};y={1};z={2}", ChunkPos[0], ChunkPos[1], ChunkPos[2]);
        }
        public int[] FetchPos()
        {
            return ChunkPos;
        }
        public int[,,] FetchAllBlocks()
        {
            return BlockReg;
        }
        public int FetchBlockReg(int x, int y, int z)
        {
            return BlockReg[x, y, z];
        }
    }

    public static class ChunkRegistry
    {
        static int ChunkWidth = 16;
        static int ChunkHeight = 16;
        static int ChunkLength = 16;
        static List<Chunk> Chunks = new List<Chunk>();
        static List<string> Chunk_Pos_Lookup = new List<string>();
        public static int FetchChunkCount()
        {
            return Chunks.Count;
        }
        public static Chunk IndexChunk(int x, int y, int z)
        {
            Chunk newChunk = new Chunk(x, y, z, ChunkWidth, ChunkHeight, ChunkLength, Chunks.Count);
            Chunks.Add(newChunk);
            Chunk_Pos_Lookup.Add(String.Format("x={0};y={1};z={2}", x, y, z));
            return newChunk;
        }
        public static Chunk FetchChunk(string pos_lookup)
        {
            if (!Chunk_Pos_Lookup.Contains(pos_lookup)) return null;
            return Chunks[Chunk_Pos_Lookup.IndexOf(pos_lookup)];
        }
        public static Chunk FetchChunk(int index)
        {
            return Chunks[index];
        }
        public static void RenderChunk(Graphics g,int chunkIndex)
        {
            Chunks[chunkIndex].RenderBlocks(g);
        }
        public static int FetchChunkID(string pos_lookup)
        {
            if (!Chunk_Pos_Lookup.Contains(pos_lookup)) return -1;
            return Chunk_Pos_Lookup.IndexOf(pos_lookup);
        }
        public static int[] FetchDimensions()
        {
            return new int[] {ChunkWidth, ChunkHeight, ChunkLength};
        }
    }
    public static class BlockRegistry
    {
        static List<Block> blocks = new List<Block>();
        static List<string> block_pos_lookup = new List<string>();
        static int BlockSize = 32;
        public static BlockState FetchBlockState(int BlockID)
        {
            return BlockstateRegistry.GetBlockstate(blocks[BlockID].FetchBlockState());
        }
        public static Block FetchBlock(int BlockID)
        {
            return blocks[BlockID];
        }
        public static int FetchBlockIDFromPos(string pos_lookup)
        {
            if (!block_pos_lookup.Contains(pos_lookup)) return -1;
            return block_pos_lookup.IndexOf(pos_lookup);
        }
        public static int[] GetDrawLocation(int x, int y, int z, float offsetX, float offsetY, float offsetZ)
        {
            return new int[]{(int)((((double)(x + offsetX) * 0.5) + ((double)(z + offsetZ) * -0.5))*(double)BlockSize),
                            (int)((((double)(x + offsetX) * 0.5) + ((double)(z + offsetZ) * 0.5) - (-offsetY + (y)))*((double)BlockSize/2.0)) };
        }
        public static void RenderBlock(Graphics g, int BlockID,int WorldX, int WorldY, int WorldZ, int ShadowLevel)
        {
            RenderBlock(g, BlockID, WorldX, WorldY, WorldZ, ShadowLevel, false);
        }
        public static void RenderBlock(Graphics g, int BlockID,int WorldX, int WorldY, int WorldZ, int ShadowLevel, bool player)
        {
            int[] locationWOffset = GetDrawLocation(WorldX, WorldY, WorldZ, PlayerObject.GetCameraPos()[0], PlayerObject.GetCameraPos()[1], PlayerObject.GetCameraPos()[2]);
            int [] location = GetDrawLocation(WorldX, WorldY, WorldZ, 0,0,0);
            VoxelRegistry.FetchVoxel(blocks[BlockID].FetchVoxelID()).Draw(g, (player? location[0] : locationWOffset[0])+ (int)(Terrain4.width / (2 * TextureManager.RenderScale)), (player ? location[1] : locationWOffset[1]) + (int)(Terrain4.height / (2 * TextureManager.RenderScale)), WorldX, WorldY, WorldZ, ShadowLevel, FetchBlockState(BlockID).hasBlocksLight());
        }
        public static int CheckInBlock(Block block,int x, int y, int z)
        {
            blocks.Add(block);
            block_pos_lookup.Add(String.Format("x={0};y={1};z={2}",x,y,z));
            return blocks.IndexOf(blocks.Last());
        }
        public static string FetchBlockString(int x, int y, int z)
        {
            return String.Format("x={0};y={1};z={2}", x, y, z);
        }
    }
    public class Block
    {
        int VoxelID;
        int BlockstateID;
        int ChunkID;
        bool render;
        public Block(int voxelID, int blockstateID, int chunkID)
        {
            VoxelID = voxelID;
            BlockstateID = blockstateID;
            ChunkID = chunkID;
        }
        public int FetchVoxelID()
        {
            return VoxelID;
        }
        public int FetchChunkID()
        {
            return ChunkID;
        }
        public int FetchBlockState()
        {
            return BlockstateID;
        }
        public void SetRender(bool render)
        {
            this.render = render;
        }
        public bool Renders()
        {
            return render;
        }
    }
}
