Voxel World

I have been always fascinated by how the world in minecraft was created, and after playing it for so long I wanted to try and recreate the same world.

Using it as a learnig experience to farther increase my knowledge of how procedural generation functions, specifically how Voxel worlds are created and how thy can be applied to the game development.







This is still an ongoing project, I will keep updating the page as i progress with the development.

In the video is shown the loading and unloading of the chunks as the player moves in the world.

I started by generating blocks and put them together in chunks, and after that i culled all the faces that weren't facing externally in order to save performance.

Then i proceded to add the noise componet to the chunk drawing.

          
  public class Block
{
    enum SideOfCube
    {
        BOTTOM,
        TOP,
        LEFT,
        RIGHT,
        FRONT,
        BACK
    };

    public enum BlockType
    {
        GRASS,
        DIRT,
        STONE,
        DIAMOND,
        BEDROCK,
        REDSTONE,
        AIR,
    };
    
    public BlockType bType;
    private GameObject _parent;
    private Vector3 _position;
    private Material _material;
    private Chunk _chunkOwner;
    public bool bIsSolid;
    

    private Vector2[,] blocksUvs =
    {
        /*grass top*/
        {
            new Vector2(0.125f, 0.375f), new Vector2(0.1875f, 0.375f), new Vector2(0.125f, 0.4375f),
            new Vector2(0.1875f, 0.4375f)
        },
        /*grass side*/
        {
            new Vector2(0.1875f, 0.9375f), new Vector2(0.25f, 0.9375f), new Vector2(0.1875f, 1.0f),
            new Vector2(0.25f, 1.0f)
        },
        /*dirt*/
        {
            new Vector2(0.125f, 0.9375f), new Vector2(0.1875f, 0.9375f), new Vector2(0.125f, 1.0f),
            new Vector2(0.1875f, 1.0f)
        },
        /*stone*/
        {new Vector2(0, 0.875f), new Vector2(0.0625f, 0.875f), new Vector2(0f, 0.9375f), new Vector2(0.0625f, 0.9375f)},
        /*diamond*/
        {new Vector2(0.125f, 0.75f),new Vector2(0.1875f, 0.75f),new Vector2(0.125f, 0.8125f),new Vector2(0.1875f, 0.8125f),},
        /*BEDROCK*/
        {new Vector2(0.0625f, 0.875f),new Vector2(0.125f, 0.875f),new Vector2(0.0625f, 0.9375f),new Vector2(0.125f, 0.9375f),},
        /*REDSTONE*/
        {new Vector2(0.1875f, 0.75f),new Vector2(0.25f, 0.75f),new Vector2(0.1875f, 0.8125f),new Vector2(0.25f, 0.8125f),}

    };

    public Block(BlockType block, Vector3 position, GameObject parent, Chunk chunk)
    {
        bType = block; 
        _position = position;
        _parent = parent;
        _chunkOwner = chunk;
        if (bType == BlockType.AIR)
        {
            bIsSolid = false;
        }
        else
        {
            bIsSolid = true;
        }
        
    }
    
        void CreateQuad(SideOfCube side)
    {
        Mesh _mesh = new Mesh();
        _mesh.name = "CustomMesh";

        Vector3[] verts = new Vector3[4];
        Vector3[] normals = new Vector3[4];
        Vector2[] uvs = new Vector2[4];
        int[] tris = new int[6];

        //UVs
        Vector2 uv00 = new Vector2(0f, 0f);
        Vector2 uv01 = new Vector2(1f, 0f);
        Vector2 uv02 = new Vector2(0f, 1f);
        Vector2 uv03 = new Vector2(1f, 1f);

        if (bType ==  BlockType.GRASS && side == SideOfCube.TOP)
        {
            uv00 = blocksUvs[0, 0];
            uv01 = blocksUvs[0, 1];
            uv02 = blocksUvs[0, 2];
            uv03 = blocksUvs[0, 3];
        }
        else if(bType == BlockType.GRASS && side == SideOfCube.BOTTOM)
        {
            uv00 = blocksUvs[(int)(BlockType.DIRT+1), 0];
            uv01 = blocksUvs[(int)(BlockType.DIRT+1), 1];
            uv02 = blocksUvs[(int)(BlockType.DIRT+1), 2];
            uv03 = blocksUvs[(int)(BlockType.DIRT+1), 3];
        }
        else
        {
            uv00 = blocksUvs[(int)(bType+1), 0];
            uv01 = blocksUvs[(int)(bType+1), 1];
            uv02 = blocksUvs[(int)(bType+1), 2];
            uv03 = blocksUvs[(int)(bType+1), 3];
        }
        
        //verts of a cube;
        Vector3 v00 = new Vector3(-0.5f, -0.5f, 0.5f);
        Vector3 v01 = new Vector3(0.5f, -0.5f, 0.5f);
        Vector3 v02 = new Vector3(0.5f, -0.5f, -0.5f);
        Vector3 v03 = new Vector3(-0.5f, -0.5f, -0.5f);
        Vector3 v04 = new Vector3(-0.5f, 0.5f, 0.5f);
        Vector3 v05 = new Vector3(0.5f, 0.5f, 0.5f);
        Vector3 v06 = new Vector3(0.5f, 0.5f, -0.5f);
        Vector3 v07 = new Vector3(-0.5f, 0.5f, -0.5f);

        switch (side)
        {
            case SideOfCube.BOTTOM:
                verts = new Vector3[] {v00, v01, v02, v03};
                normals = new Vector3[] {Vector3.down, Vector3.down, Vector3.down, Vector3.down};
                uvs = new Vector2[] {uv03, uv02, uv00, uv01};
                tris = new int[] {3, 1, 0, 3, 2, 1};
                break;
            case SideOfCube.TOP:
                verts = new Vector3[] {v07, v06, v05, v04};
                normals = new Vector3[] {Vector3.up, Vector3.up, Vector3.up, Vector3.up};
                uvs = new Vector2[] {uv03, uv02, uv00, uv01};
                tris = new int[] {3, 1, 0, 3, 2, 1};
                break;
            case SideOfCube.LEFT:
                verts = new Vector3[] {v07, v04, v00, v03};
                normals = new Vector3[] {Vector3.left, Vector3.left, Vector3.left, Vector3.left};
                uvs = new Vector2[] {uv03, uv02, uv00, uv01};
                tris = new int[] {3, 1, 0, 3, 2, 1};
                break;
            case SideOfCube.RIGHT:
                verts = new Vector3[] {v05, v06, v02, v01};
                normals = new Vector3[] {Vector3.right, Vector3.right, Vector3.right, Vector3.right};
                uvs = new Vector2[] {uv03, uv02, uv00, uv01};
                tris = new int[] {3, 1, 0, 3, 2, 1};
                break;
            case SideOfCube.FRONT:
                verts = new Vector3[] {v04, v05, v01, v00};
                normals = new Vector3[] {Vector3.forward, Vector3.forward, Vector3.forward, Vector3.forward};
                uvs = new Vector2[] {uv03, uv02, uv00, uv01};
                tris = new int[] {3, 1, 0, 3, 2, 1};
                break;
            case SideOfCube.BACK:
                verts = new Vector3[] {v06, v07, v03, v02};
                normals = new Vector3[] {Vector3.back, Vector3.back, Vector3.back, Vector3.back};
                uvs = new Vector2[] {uv03, uv02, uv00, uv01};
                tris = new int[] {3, 1, 0, 3, 2, 1};
                break;

        }

        /* normals = new Vector3[]
         {
             Vector3.forward, 
             Vector3.forward, 
             Vector3.forward, 
             Vector3.forward
         };
         
         verts= new Vector3[] {v04, v05, v01, v00};
         uvs = new Vector2[] {uv03, uv02, uv00, uv01 };
         tris = new int[]{3,1,0,3,2,1};*/

        _mesh.vertices = verts;
        _mesh.normals = normals;
        _mesh.uv = uvs;
        _mesh.triangles = tris;

        _mesh.RecalculateBounds();

        GameObject quad = new GameObject("Quad");
        quad.transform.position = _position;
        quad.transform.parent = _parent.transform;
        MeshFilter _meshFilter = (MeshFilter) quad.AddComponent(typeof(MeshFilter));
        /*After combining quads into a singular mesh we don't need a renderer for each single quad, one is added to the whole mesh already*/
        //MeshRenderer _rend = quad.AddComponent(typeof(MeshRenderer)) as MeshRenderer;
        //_rend.material = _material;
        _meshFilter.mesh = _mesh;
    }

        public void Draw()
        {
            if (bType == BlockType.AIR)
            {
                return;
            }
            if (!HasSolidNeighbor((int)_position.x, (int)_position.y, (int)_position.z +1)){
                CreateQuad(SideOfCube.FRONT);
            }
            if (!HasSolidNeighbor((int)_position.x, (int)_position.y, (int)_position.z -1)){
                CreateQuad(SideOfCube.BACK);
            }
            if (!HasSolidNeighbor((int)_position.x, (int)_position.y +1, (int)_position.z)){
                CreateQuad(SideOfCube.TOP);
            }
            if (!HasSolidNeighbor((int)_position.x, (int)_position.y- 1, (int)_position.z)){
                CreateQuad(SideOfCube.BOTTOM);
            }
            if (!HasSolidNeighbor((int)_position.x - 1, (int)_position.y, (int)_position.z)){
                CreateQuad(SideOfCube.LEFT);
            }
            if (!HasSolidNeighbor((int)_position.x +1 , (int)_position.y, (int)_position.z)){
                CreateQuad(SideOfCube.RIGHT);
            }
        }


        int ConvertBlockIndexToLocal(int i)
        {
            if (i == -1)
            {
                i = World.chunkSize - 1;
            }else if (i == World.chunkSize)
            {
                i = 0;
            }

            return i;
        }
        bool HasSolidNeighbor(int neighborX, int neighborY, int neighborZ)
        {
            Block[,,] chunks;
            chunks = _chunkOwner._chunkData;
            if (neighborX < 0|| neighborX >= World.chunkSize || neighborY < 0|| neighborY >= World.chunkSize|| neighborZ< 0|| neighborZ >= World.chunkSize )
            {
                Vector3 neighborChunkPosition = this._parent.transform.position +
                                                new Vector3((neighborX - (int) _position.x) * World.chunkSize,
                                                    (neighborY - (int) _position.y) * World.chunkSize,
                                                    (neighborZ - (int) _position.z) * World.chunkSize);

                string neighborName = World.BuildChunkName(neighborChunkPosition);

                neighborX = ConvertBlockIndexToLocal(neighborX);
                neighborY = ConvertBlockIndexToLocal(neighborY);
                neighborZ = ConvertBlockIndexToLocal(neighborZ);

                Chunk _neightborChunk;
                if (World.chunks.TryGetValue(neighborName, out _neightborChunk))
                {
                    chunks = _neightborChunk._chunkData;
                }
                else
                    return false;
            }
            else
            {
                chunks = _chunkOwner._chunkData;
            }
            try
            {
                return chunks[neighborX, neighborY, neighborZ].bIsSolid;
            }
            catch (System.IndexOutOfRangeException) {}

            return false;
        }
}
            
        
          
  public class Chunk
{
    public Material _material;
    public Block[,,] _chunkData;
    public GameObject _chunk;
    public enum chunckStatus { DRAW, DONE, KEEP };
    public chunckStatus status;
    private BlockData _blockData;

    string CreateChunkFileName(Vector3 chunckPosition)
    {
        return Application.persistentDataPath + "/savedata/Chunck_" + (int) chunckPosition.x + "_" +
               (int) chunckPosition.y + "_" + (int) chunckPosition.z + "_" + World.chunkSize + "_" + World.radius +
               "_" + ".dat";
    }

    bool Load()
    {
        string chunkFile = CreateChunkFileName(_chunk.transform.position);
        if (File.Exists(chunkFile))
        {
            BinaryFormatter bf = new BinaryFormatter();
            FileStream file = File.Open(chunkFile, FileMode.Open);
            _blockData = new BlockData();
            _blockData = (BlockData) bf.Deserialize(file);
            Debug.Log("Loading chunk file at: " + chunkFile);
            file.Close();
            return true;
        }
        return false;
    }

    public void Save()
    {
        string chunkFile = CreateChunkFileName(_chunk.transform.position);
        if (!File.Exists(chunkFile))
        {
            Directory.CreateDirectory(Path.GetDirectoryName(chunkFile));
        }
        BinaryFormatter bf = new BinaryFormatter();
        FileStream file = File.Open(chunkFile, FileMode.OpenOrCreate);
        _blockData = new BlockData();
        bf.Serialize(file, _blockData);
        file.Close();
    }

    void BuildChunk()
    {
        bool dataFromFile = false;
        dataFromFile = Load();
        
        _chunkData = new Block[World.chunkSize,World.chunkSize,World.chunkSize];
        /*Create Chunk Data*/
        for (int z = 0; z < World.chunkSize; z++)
        {
            for (int y = 0; y < World.chunkSize; y++)
            {
                for (int x = 0; x < World.chunkSize; x++)
                {
                    Vector3 pos = new Vector3(x,y,z);
                    Vector3 chunkPosition = _chunk.transform.position;
                    int worldX = (int) (x + chunkPosition.x);
                    int worldY = (int) (y + chunkPosition.y);
                    int worldZ = (int) (z + chunkPosition.z);

                    if (dataFromFile)
                    {
                        _chunkData[x,y,z] = new Block(_blockData.blockMatrix[x,y,z], pos, _chunk.gameObject, this);
                        continue;
                    }
                    
                    //Debug.Log(GenerationUtils.GenerateHeight(worldX, worldZ));
                    //if (GenerationUtils._BrownianMotion3D(worldX, worldY, worldZ, 3, 0.5f) < 0.40f)
                    if (GenerationUtils.BrownianMotion3D(worldX, worldY, worldZ, 0.1f, 3) < 0.42f)
                    {
                        _chunkData[x,y,z] = new Block(Block.BlockType.AIR, pos, _chunk.gameObject, this);
                    }
                    else if (worldY <= GenerationUtils.GenerateStoneHeight(worldX, worldZ))
                    {
                        if (GenerationUtils.BrownianMotion3D(worldX,worldY,worldZ, 0.1f, 2)< 0.4f && worldY <= 40)
                        {
                            _chunkData[x,y,z] = new Block(Block.BlockType.DIAMOND, pos, _chunk.gameObject, this);
                            Debug.Log("Placing Diamonds");
                        }
                        else if (GenerationUtils.BrownianMotion3D(worldX, worldY, worldZ, 0.3f, 3) < 0.41f &&
                                 worldY <= 20)
                        {
                            _chunkData[x,y,z] = new Block(Block.BlockType.REDSTONE, pos, _chunk.gameObject, this);
                            Debug.Log("Placing Red stone");
                        }
                        else
                        {
                            _chunkData[x, y, z] = new Block(Block.BlockType.STONE, pos, _chunk.gameObject, this);
                        }
                    }
                    else if (worldY == GenerationUtils.GenerateHeight(worldX, worldZ))
                    {
                        _chunkData[x, y, z] = new Block(Block.BlockType.GRASS, pos, _chunk.gameObject, this);
                    }
                    else if (worldY <= GenerationUtils.GenerateHeight(worldX, worldZ))
                    {
                        _chunkData[x, y, z] = new Block(Block.BlockType.DIRT, pos, _chunk.gameObject, this);
                    }
                    else
                    {
                        _chunkData[x,y,z] = new Block(Block.BlockType.AIR, pos, _chunk.gameObject, this);
                    }

                    status = chunckStatus.DRAW;
                }
            }
        }
       
       
      
    }

    public void DrawChunk()
    {
        /*Draw Chunk*/
        for (int z = 0; z < World.chunkSize; z++)
        {
            for (int y = 0; y < World.chunkSize; y++)
            {
                for (int x = 0; x < World.chunkSize; x++)
                {
                    _chunkData[x,y,z].Draw();
                }
            }
        }
        //Old drawing Logic
        /*for (int z = 0; z < sizeZ; z++)
        {
            for (int y = 0; y < sizeY; y++)
            {
                for (int x = 0; x < sizeX; x++)
                {
                    Vector3 pos = new Vector3(x,y,z);
                    Block _block = new Block(Block.BlockType.DIRT, pos, this.gameObject, _material);
                    _block.Draw();
                    yield return null;
                }
            }
        }*/
        CombineQuads(); 
        MeshCollider _blockCollider = _chunk.gameObject.AddComponent(typeof(MeshCollider)) as MeshCollider;
        _blockCollider.sharedMesh = _chunk.transform.GetComponent< MeshFilter>().mesh;
        status = chunckStatus.DONE;

    }

    public Chunk(Vector3 position, Material material)
    {
        _chunk = new GameObject(World.BuildChunkName(position));
        _chunk.transform.position = position;
        _material = material;
        BuildChunk();
    }
    

    //batching all the triangles together makes it easier for unity to handle the drawing, also have less draw calls
    //doing this by combining all the quads together
    void CombineQuads()
    {
        MeshFilter[] _meshFilters = _chunk.GetComponentsInChildren< MeshFilter>();
        CombineInstance[] _combine = new CombineInstance[_meshFilters.Length];
        int i = 0;
        while (i < _meshFilters.Length)
        {
            _combine[i].mesh = _meshFilters[i].sharedMesh;
            _combine[i].transform = _meshFilters[i].transform.localToWorldMatrix;
            i++;
        }
        //after creating the combine array with all the previously created quads we give it a meshFilter, a mesh renderer
        //and being the case that we don't need the quads to be rendered any more we can destroy them;
        MeshFilter _mf = (MeshFilter) _chunk.gameObject.AddComponent(typeof(MeshFilter));
        _mf.mesh = new Mesh();
        _mf.mesh.CombineMeshes(_combine);

        MeshRenderer _meshRenderer = _chunk.gameObject.AddComponent(typeof(MeshRenderer)) as MeshRenderer;
        _meshRenderer.material = _material;

        foreach (Transform quad in _chunk.transform)
            GameObject.Destroy(quad.gameObject);
    }

}
            
        

Then i proceeded in smoothing out the noise curve getting a more pleasing profile of the terrain as well as adding a way to generate caves.

          
public class World : MonoBehaviour
{
    public GameObject player;
    public Material textureAtlas;
    
    public static int chunkSize = 16;
    public static int columnHeight = 16;
    public static int worldSize = 4;
    public static int radius = 4;
    //public Slider slider;
    //public Camera cam;
    //public Button playButton;
    private bool isFirstInstanceBuild = true;
    private bool isBuilding = false;

    public Vector3 lastRecordedBuildPosition;
    private CoroutineQueue _queue;
    public static uint maxCoroutinesactive = 1000;

    //public static Dictionary< string, Chunk> chunks;
    public static ConcurrentDictionary< string, Chunk> chunks;
    public static List< string> chuncksToRemove = new List< string>();
    
    public static string BuildChunkName(Vector3 position)
    {
        return (int) position.x + "_" + (int) position.y + "_" + (int) position.z;
    }
    
void BuildChunkAt(int x, int y, int z)
    {
        Vector3 _chunkPosition = new Vector3(x * chunkSize, y* chunkSize, z * chunkSize);
        string _chunkName = BuildChunkName(_chunkPosition);
        Chunk _chunk;
        if (!chunks.TryGetValue(_chunkName, out _chunk))
        {
            _chunk = new Chunk(_chunkPosition, textureAtlas);
            _chunk._chunk.transform.parent = transform;
            chunks.TryAdd(_chunk._chunk.name, _chunk);
        }
    }

    IEnumerator BuildRecursiveWorld(int x, int y, int z, int rad)
    {
        rad--;
        if (rad <= 0)
        {
            Debug.Log("Radius = " + rad);
            yield break;
        }
        BuildChunkAt(x, y, z +1);
        _queue.Run(BuildRecursiveWorld(x, y, z + 1, rad));
        yield return null;
        
        BuildChunkAt(x, y, z -1);
        _queue.Run(BuildRecursiveWorld(x, y, z - 1, rad));
        yield return null;
        
        BuildChunkAt(x+1, y, z);
        _queue.Run(BuildRecursiveWorld(x-1, y, z , rad));
        yield return null;
        
        BuildChunkAt(x- 1, y, z);
        _queue.Run(BuildRecursiveWorld(x+ 1, y, z, rad));
        yield return null;
        
        BuildChunkAt(x, y -1, z );
        _queue.Run(BuildRecursiveWorld(x, y - 1, z, rad));
        yield return null;
        
        BuildChunkAt(x, y + 1, z);
        _queue.Run(BuildRecursiveWorld(x, y + 1 , z, rad));
        yield return null;
    }

    IEnumerator DrawChunks()
    {
        foreach (KeyValuePair< string, Chunk> chunk in chunks)
        {
            if (chunk.Value.status == Chunk.chunckStatus.DRAW)
            {
                chunk.Value.DrawChunk();
            }

            if (chunk.Value._chunk && Vector3.Distance(player.transform.position, chunk.Value._chunk.transform.position)> radius*chunkSize)
            {
                chuncksToRemove.Add(chunk.Key);
            }
            yield return null;
        }
    }
    private void BuildNearPlayer()
    {
        StopCoroutine("BuildRecursiveWorld");
        _queue.Run(BuildRecursiveWorld((int) (player.transform.position.x / chunkSize),
            (int) (player.transform.position.y / chunkSize),
            (int) (player.transform.position.z / chunkSize), radius));
    }

    IEnumerator RemoveOldChunks()
    {
        for (int i = 0; i < chuncksToRemove.Count; i++)
        {
            string chunckName = chuncksToRemove[i];
            Chunk chunck;
            if (chunks.TryGetValue(chunckName, out chunck))
            {
                Destroy(chunck._chunk);
                chunck.Save();
                chunks.TryRemove(chunckName, out chunck);
                yield return null;
            }
        }
    }
    
    void Start()
    {
        _queue = new CoroutineQueue(maxCoroutinesactive, StartCoroutine);
        GenerationUtils.perlin();
        Vector3 _playerPosition = player.transform.position;
        player.transform.position = new Vector3(_playerPosition.x,
            GenerationUtils.GenerateHeight(_playerPosition.x, _playerPosition.z) + 1, _playerPosition.z);
        lastRecordedBuildPosition = player.transform.position;
        player.SetActive(false);
        isFirstInstanceBuild = true;
        chunks = new ConcurrentDictionary< string, Chunk>();
        transform.position = Vector3.zero;
        transform.rotation = Quaternion.identity;
        

        BuildChunkAt((int) (player.transform.position.x / chunkSize), (int) (player.transform.position.y/ chunkSize),
            (int) (player.transform.position.z / chunkSize));

        _queue.Run(DrawChunks());

        _queue.Run(BuildRecursiveWorld((int) (player.transform.position.x / chunkSize),
            (int) (player.transform.position.y / chunkSize),
            (int) (player.transform.position.z / chunkSize), radius));
    }
    private void Update()
    {
        Vector3 _movement = lastRecordedBuildPosition - player.transform.position;
        if (_movement.magnitude > chunkSize)
        {
            lastRecordedBuildPosition = player.transform.position;
            BuildNearPlayer();
        }
        
        if (!player.activeSelf)
        {
            player.SetActive(true);
            isFirstInstanceBuild = false;
        }
        _queue.Run(DrawChunks());
        _queue.Run(RemoveOldChunks());
    }

            
        
          
public class GenerationUtils
{
    private static int maxHeight = 150;
    private static float increment = 0.005f;
    private static int octaves = 4;
    private static float persistence = 0.5f;
    private static int repeat = 0;
    

    public static int GenerateHeight(float x, float z)
    {
        float _height = Map(0, maxHeight, 0, 1, BrownianMotion(x * increment , z *increment, octaves, persistence));
        return (int) _height;
    }
    public static int GenerateStoneHeight(float x, float z)
    {
        float _height = Map(0, maxHeight-20, 0, 1, BrownianMotion(x * increment * 2, z *increment*2, octaves+1, persistence));
        return (int) _height;
    }

    static float Map(float newMin, float newMax, float originalMin, float originalMax, float value)
    {
        return Mathf.Lerp(newMin, newMax, Mathf.InverseLerp(originalMin, originalMax, value));
    }

    public static float BrownianMotion(float x, float z, int octave, float persistence)
    {
        float _total = 0f;
        float _frequency = 1f;
        float _amplitude = 1f;
        float maxValue = 0;
        float _offset = 32000f;
        for (int i = 0; i < octave; i++)
        {
            _total += Perlin((x + _offset) * _frequency, (z + _offset) * _frequency, 0f) * _amplitude;
            maxValue += _amplitude;
            _amplitude *= persistence;
            _frequency *= 2;
        }

        return _total / maxValue;
    } 

    public static float BrownianMotion3D(float x, float y, float z)
    {
        float XY = BrownianMotion(x * increment*10, y * increment*10, 3,  0.5f);
        float YZ = BrownianMotion(y * increment*10, z * increment*10, 3,  0.5f);
        float XZ = BrownianMotion(z * increment*10, x * increment*10, 3,  0.5f);

        float YX = BrownianMotion(y * increment*10, x * increment*10, 3, 0.5f);
        float ZY = BrownianMotion(z * increment*10, y * increment*10, 3, 0.5f);
        float ZX = BrownianMotion(z * increment*10, x * increment*10, 3, 0.5f);

        return (XY + YZ + XZ + YX + ZY + ZX) / 6.0f;
    }
    public static float BrownianMotion3D(float x, float y, float z, float smoothing, int octaves)
    {
        float XY = BrownianMotion(x * smoothing, y * smoothing, octaves,  0.5f);
        float YZ = BrownianMotion(y * smoothing, z * smoothing, octaves,  0.5f);
        float XZ = BrownianMotion(z * smoothing, x * smoothing, octaves,  0.5f);

        float YX = BrownianMotion(y * smoothing, x * smoothing, octaves, 0.5f);
        float ZY = BrownianMotion(z * smoothing, y * smoothing, octaves, 0.5f);
        float ZX = BrownianMotion(z * smoothing, x * smoothing, octaves, 0.5f);

        return (XY + YZ + XZ + YX + ZY + ZX) / 6.0f;
    }

    /*public static float _BrownianMotion3D(float x, float y, float z, int octave, float persistence)
    {
        float _total = 0f;
        float _frequency = 1f;
        float _amplitude = 1f;
        float maxValue = 0;

        for (int i = 0; i < octave; i++)
        {
            _total += Perlin(x * _frequency * increment* 10 , y * _frequency * increment* 10, z * _frequency * increment* 10) * _amplitude;
            maxValue += _amplitude;
            _amplitude *= persistence;
            _frequency *= 2;
        }

        return _total / maxValue;
    }*/
    

    /*PERLIN NOISE*/
    
    public static float Perlin(float x, float y, float z)
    {
        
        if (repeat >0)
        {
            x = x % repeat;
            y = y % repeat;
            z = z % repeat;
        }
        int xi = (int) x & 255;
        int yi = (int) y & 255;
        int zi = (int) z & 255;
        float xf = x - (int) x;
        float yf = y - (int) y;
        float zf = z - (int) z;

        float u = Fade(xf);
        float v = Fade(yf);
        float w = Fade(zf);

        int a = (p[xi] + yi);
        int aa = (p[a] + zi);                                             
        int ab =( p[a + 1] + zi);                                            
        int b = (p[xi + 1] + yi);
        int ba = (p[b] + zi);                                             
        int bb =( p[b + 1] + zi);                                            
                                                                                
                                                                                                                                                // This is where the "magic" happens.  We calculate a new set of p[] values and use that to get
                                                                                                                                                // our final gradient values.  Then, we interpolate between those gradients with the u value to get
        float x1, x2, y1, y2;                                                                                                                   // 4 x-values.  Next, we interpolate between the 4 x-values with v to get 2 y-values.  Finally,
        x1 = Lerp(_Gradient(p[aa], xf, yf, zf), _Gradient(p[ba], xf - 1, yf, zf), u);                              // we interpolate between the y-values to get a z-value.
        x2 = Lerp(_Gradient(p[ab], xf, yf - 1, zf), _Gradient(p[bb], xf - 1, yf - 1, zf), u);                      // When calculating the p[] values, remember that above, p[a+1] expands to p[xi]+yi+1 -- so you are
                                                                                                                                                // essentially adding 1 to yi.  Likewise, p[ab+1] expands to p[p[xi]+yi+1]+zi+1] -- so you are adding
        y1 = Lerp(x1, x2, v);                                                                                                         // to zi.  The other 3 parameters are your possible return values (see grad()), which are actually   
                                                                                                                                                // the vectors from the edges of the unit cube to the point in the unit cube itself.
        x1 = Lerp(_Gradient(p[aa +1], xf, yf, zf - 1), _Gradient(p[ba +1], xf - 1, yf, zf - 1), u);
        x2 = Lerp(_Gradient(p[ab +1], xf, yf - 1, zf - 1), _Gradient(p[bb + 1], xf - 1, yf - 1, zf - 1), u);

        y2 = Lerp(x1, x2, w);

        return (Lerp(y1, y2, w) + 1) / 2;
    }
    
    private static readonly int[] permutation = { 151,160,137,91,90,15,                 // Hash lookup table as defined by Ken Perlin.  This is a randomly
        131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,    // arranged array of all numbers from 0-255 inclusive.
        190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
        88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
        77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
        102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
        135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
        5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
        223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
        129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
        251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
        49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
        138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180/*,151*/
    };

    private static int[] p;

    public static void perlin()
    {
        p = new int[512];
        for (int x = 0; x < 512; x++)
        {
            p[x] = permutation[x % 256];
        }
    }
    // Fade function as defined by Ken Perlin.  This eases coordinate values
    // so that they will ease towards integral values.  This ends up smoothing
    // the final output.
    // // 6t^5 - 15t^4 + 10t^3
    public static float Fade(float t)
    {
        return t * t * t * (t * (t * 6 - 15) + 10);
    }

    public static int Increment(int num)
    {
        num++;
        if (repeat > 0)
        {
            num %= repeat;
        }
        return num;
    }

    public static float _Gradient(int hash, float x, float y, float z)
    {
        int h = hash & 15;                              // Take the hashed value and take the first 4 bits of it (15 == 0b1111)
        float u = h < 8 ? x : y;                        // If the most significant bit (MSB) of the hash is 0 then set u = x.  Otherwise y.
        float v;                                        // In Ken Perlin's original implementation this was another conditional operator (?:).
                                                        // expanded it for readability.
                                                        
        if (h < 4)                                      // If the first and second significant bits are 0 set v = y
        {                                               
            v = y;
        }
        else if (h ==12 ||  h == 14)                    // If the first and second significant bits are 1 set v = x
        {
            v = x;
        }
        else                                            // If the first and second significant bits are not equal (0/1, 1/0) set v = z
        {
            v = z;
        }

        return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v);

    }

    public static float Lerp(float a, float b, float x)
    {
        return a + x * (b - a);
    }