Multiple procedural terrain problem

The following script is part of a multi-terrain generation system I’m working on. It will be released to the community for free when it works.

It should create a single heightmap that is then split down into chunks to be applied to the individual terrains. I realise that the edges still won’t line up yet but I can’t sort that out until I’ve dealt with a more pressing problem.

The code simply copies the same piece of heightmap on all terrains. After a couple of weeks staring at the same lines of code and I’ve hit the point where I can no longer see the forest for the trees. I know the answer is something simple but I can’t for the life of me see what it is. A fresh pair of eyes may just find the answer.

    using UnityEngine;
    using System;
    using System.Collections;
    
    public class MultiTerrain02 : MonoBehaviour {
    
    	TerrainData[] terrData;
    	GameObject[] terrList;
    	int terrListCnt;

        [SerializeField]
    	public int maxX=3;
    
    	[SerializeField]
    	public int maxZ=3;
    
    	int terrWidth;
    	int terrLength;
    	int heightmapWidth;
    	int heightmapHeight;
    	int width;
    	int height;
    
    	void Start () 
    	{
    		GenerateTerrains();
    		FractalTerrains();
    	}
    
    	void GenerateTerrains()
    	{
    		int cntX;
    		int cntZ;
    		terrData=new TerrainData[maxX*maxZ];
    		terrList=new GameObject[maxX*maxZ];
    		terrListCnt=0;
    		for(cntZ=0;cntZ<maxZ;cntZ++)
    		{
    			for(cntX=0;cntX<maxX;cntX++)
    			{	
    				if(cntZ==0&&cntX==0)
    				{
    					terrData[0]=GetComponent<Terrain>().terrainData;
    					terrList[0]=this.gameObject;
    					terrListCnt++;
    					cntX++;
    				}
    				terrData[terrListCnt] = terrData[0];
                                terrList[terrListCnt]=Terrain.CreateTerrainGameObject(terrData[terrListCnt]);
    				terrList[terrListCnt].name="terrain"+terrListCnt;
    				terrWidth=(int)terrList[0].GetComponent<Terrain>().terrainData.size.x;
    				terrLength=(int)terrList[0].GetComponent<Terrain>().terrainData.size.z;
terrList[terrListCnt].transform.Translate(cntX*terrWidth,0f,cntZ*terrLength);
    				terrListCnt++;
    			}
    		}
    		terrWidth=(int)terrList[0].GetComponent<Terrain>().terrainData.size.x;
    		terrLength=(int)terrList[0].GetComponent<Terrain>().terrainData.size.z;
    		heightmapWidth=terrList[0].GetComponent<Terrain>().terrainData.heightmapWidth;
    		heightmapHeight=terrList[0].GetComponent<Terrain>().terrainData.heightmapHeight;
    	}
    
    	void FractalTerrains()
    	{
    	width = heightmapWidth*maxX;
    	height = heightmapHeight*maxZ;
    		
    	float[,] grayArrayFull = new float[width,height];	
    	float[,,] grayArray = new float[maxX*maxZ,width,height];	
    	float[,] grayArrayChunk = new float[heightmapWidth,heightmapHeight];	
    
    	int tileNum=0;	
    	int tileX=0;
    	int tileZ=0;
    	float roughness=50f;
    	/////////////////Generate heights
    		
    	grayArrayFull=Generate(width,height,roughness);
    		
    	for (int z = 0;z<height;z++)
    	{
    		for (int x = 0;x<width;x++)
    		{
    			//Debug.Log("heightmapWidth  "+heightmapWidth+"   tileNum  "+tileNum+"     x  "+x+"    z  "+z+"    tileX  "+tileX+"    tileZ  "+tileZ);
    			grayArray[tileNum,tileX,tileZ]=grayArrayFull[x,z];
    			if(tileX!=heightmapWidth-1)
    			{
    				tileX++;	
    			}
    			else
    			{
    				tileX=0;
    				tileNum++;
    			}
    			if(x==width-1)
    			{
    				tileNum=tileNum-maxX;
    			}
    		}
    		if(tileZ!=heightmapWidth-1)
    		{
    			tileZ++;
    		}
    		else
    		{
    			tileZ=0;
    			tileNum=tileNum+maxX;
    		}
    	}		
    		
    		/////////	Apply the data 
    		for (int tileCnt=0;tileCnt<maxX*maxZ;tileCnt++)
    		{
    			for (int z = 0;z<heightmapHeight;z++)
    			{	
    				for (int x = 0;x<heightmapWidth;x++)
    				{
    //					Debug.Log("tileCnt "+tileCnt+"    x  "+x+"    z  "+z+"    value  "+grayArray[tileCnt,x,z]);
    					grayArrayChunk[x,z]=grayArray[tileCnt,x,z];
    				}
    			}
    		terrList[tileCnt].GetComponent<Terrain>().terrainData.SetHeights(0,0,grayArrayChunk);
    		terrList[tileCnt].GetComponent<Terrain>().Flush();
    		}
    		/////////  Set the neighbouring terrains
    	int zCnt=0;
    	int xCnt=0;
    		for (int tileCnt=0;tileCnt<maxX*maxZ;tileCnt++)
    		{
    			bool left=false;
    			bool right=false;
    			bool top=false;
    			bool bottom=false;
    			Terrain leftTerr;
    			Terrain rightTerr;
    			Terrain topTerr;
    			Terrain bottomTerr;
    			///////  check
    			if(xCnt!=0)
    			{left=true;}
    			if(xCnt!=maxX-1)
    			{right=true;}
    			if(zCnt!=0)
    			{bottom=true;}
    			if(zCnt!=maxZ-1)
    			{top=true;}
    				/////   set neighbours	
    			if(left)
    				{leftTerr=terrList[tileCnt-1].GetComponent<Terrain>();}
    				else
    					{leftTerr=null;}
    			if(right)
    				{rightTerr=terrList[tileCnt+1].GetComponent<Terrain>();}
    				else
    					{rightTerr=null;}
    			if(top)
    				{topTerr=terrList[tileCnt+maxX].GetComponent<Terrain>();}
    				else
    					{topTerr=null;}
    			if(bottom)
    				{bottomTerr=terrList[tileCnt-maxX].GetComponent<Terrain>();}
    				else
    					{bottomTerr=null;}
    	//		Debug.Log("tileCnt  "+tileCnt+"     xCnt  "+xCnt+"    zCnt  "+zCnt);	
    	//		Debug.Log("left  "+left+"     top  "+top+"    right  "+right+"    bottom  "+bottom);	
    			terrList[tileCnt].GetComponent<Terrain>().SetNeighbors(leftTerr,topTerr,rightTerr,bottomTerr);	
    			terrList[tileCnt].GetComponent<Terrain>().Flush();
    			/////  update counters
    			if(xCnt!=maxX-1)
    			{
    				xCnt++;
    			}
    			else
    			{
    				xCnt=0;
    				zCnt++;
    			}
    		}
    	}
    	public float gRoughness;
    	public float gBigSize;
            public float[,] Generate(int iWidth, int iHeight, float iRoughness)
            {
                float c1, c2, c3, c4;
                float[,] points = new float[iWidth+1, iHeight+1];
                //Assign the four corners of the intial grid random color values
                //These will end up being the colors of the four corners
                c1 = UnityEngine.Random.value;
                c2 = UnityEngine.Random.value;
                c3 = UnityEngine.Random.value;
                c4 = UnityEngine.Random.value;;
                gRoughness = iRoughness;
                gBigSize = iWidth + iHeight;
                DivideGrid(ref points, 0, 0, iWidth, iHeight, c1, c2, c3, c4);
                return points;
            }
            public void DivideGrid(ref float[,] points, float x, float y, float width, float height, float c1, float c2, float c3, float c4)
            {
                float Edge1, Edge2, Edge3, Edge4, Middle;
                float newWidth = (float)Math.Floor(width / 2);
                float newHeight = (float)Math.Floor(height / 2);
                if (width > 1 || height > 1)
                {
                    Middle = ((c1 + c2 + c3 + c4) / 4)+Displace(newWidth + newHeight);	//Randomly displace the midpoint!
                    Edge1 = ((c1 + c2) / 2);	//Calculate the edges by averaging the two corners of each edge.
                    Edge2 = ((c2 + c3) / 2);
                    Edge3 = ((c3 + c4) / 2);
                    Edge4 = ((c4 + c1) / 2);//
                    //Make sure that the midpoint doesn't accidentally "randomly displaced" past the boundaries!
                    Middle= Rectify(Middle);
                    Edge1 = Rectify(Edge1);
                    Edge2 = Rectify(Edge2);
                    Edge3 = Rectify(Edge3);
                    Edge4 = Rectify(Edge4);
                    //Do the operation over again for each of the four new grids.
                    DivideGrid(ref points, x, y, newWidth, newHeight, c1, Edge1, Middle, Edge4);
                    DivideGrid(ref points, x + newWidth, y, width - newWidth, newHeight, Edge1, c2, Edge2, Middle);
                    DivideGrid(ref points, x + newWidth, y + newHeight, width - newWidth, height - newHeight, Middle, Edge2, c3, Edge3);
                    DivideGrid(ref points, x, y + newHeight, newWidth, height - newHeight, Edge4, Middle, Edge3, c4);
                }
                else	//This is the "base case," where each grid piece is less than the size of a pixel.
                {
                    //The four corners of the grid piece will be averaged and drawn as a single pixel.
                    float c = (c1 + c2 + c3 + c4) / 4;
                    points[(int)(x), (int)(y)] = c;
                    if (width == 2)
                    {
                        points[(int)(x+1), (int)(y)] = c;
                    }
                    if (height == 2)
                    {
                        points[(int)(x), (int)(y+1)] = c;
                    }
                    if ((width == 2) && (height == 2))
                    {
                        points[(int)(x + 1), (int)(y+1)] = c;
                    }
                }
            }
           private float Rectify(float iNum)
            {
                if (iNum < 0)
                {
                    iNum = 0;
                }
                else if (iNum > 1.0)
                {
                    iNum = 1.0f;
                }
                return iNum;
            }
            private float Displace(float SmallSize)
            {
                float Max = SmallSize/ gBigSize * gRoughness;
                return (float)(UnityEngine.Random.value - 0.5) * Max;
            }	
    }

The crux of this problem was that I was copying the TerrainData of the first terrain piece onto the rest. The code was also full of other bugs but most of these have been sorted now. For anyone else who stumbles over this question looking for multi-terrain script this following code works. Attach it to a blank terrain and press play.

using UnityEngine;

using System;

using System.Collections;



public class MultiTerrain02 : MonoBehaviour {

	

	TerrainData[] terrData;  //  Array of terrain data for setting heights

	GameObject[] terrList;  //  List of terrains for instantiating

	bool[,] terrNeighbours;



	int terrListCnt;

	

	[SerializeField]

	public int maxX=3;  //  Number of terrains to generate

	

	[SerializeField]

	public int maxZ=3;

	

	

	int terrWidth;  // Used to space the terrains when instantiating

	int terrLength;  

	int heightmapWidth;  //The size of an individual heightmap

	int heightmapHeight;

	int width;  //Total size of heightmaps combined

	int height;

	

	

	void Start () 

	{

		GenerateTerrains();  

		FractalTerrains();  

	}

	

	void GenerateTerrains()

	{

		int cntX;

		int cntZ;

		terrData=new TerrainData[maxX*maxZ];

		terrList=new GameObject[maxX*maxZ];

		terrListCnt=0;

		for(cntZ=0;cntZ<maxZ;cntZ++)

		{

			for(cntX=0;cntX<maxX;cntX++)

			{	

				if(cntZ==0&&cntX==0)

				{

					terrData[0]=GetComponent<Terrain>().terrainData;

					terrList[0]=this.gameObject;

					terrList[terrListCnt].name="Terrain"+terrListCnt;

					terrListCnt++;

					cntX++;

				}

				terrData[terrListCnt] = new TerrainData() as TerrainData;

				terrData[terrListCnt].heightmapResolution = terrData[0].heightmapResolution;

				terrData[terrListCnt].size = terrData[0].size;

				terrList[terrListCnt]=Terrain.CreateTerrainGameObject(terrData[terrListCnt]);

				terrList[terrListCnt].name="Terrain"+terrListCnt;

				terrWidth=(int)terrList[0].GetComponent<Terrain>().terrainData.size.x;

				terrLength=(int)terrList[0].GetComponent<Terrain>().terrainData.size.z;

				terrList[terrListCnt].transform.Translate(cntX*terrWidth,0f,cntZ*terrLength);

				terrListCnt++;

			}

		}

		terrWidth=(int)terrList[0].GetComponent<Terrain>().terrainData.size.x;

		terrLength=(int)terrList[0].GetComponent<Terrain>().terrainData.size.z;

		heightmapWidth=terrList[0].GetComponent<Terrain>().terrainData.heightmapWidth;

		heightmapHeight=terrList[0].GetComponent<Terrain>().terrainData.heightmapHeight;

	int zCnt=0;

	int xCnt=0;

		for (int tileCnt=0;tileCnt<maxX*maxZ;tileCnt++)

		{

			bool left=false;

			bool right=false;

			bool top=false;

			bool bottom=false;

			Terrain leftTerr;

			Terrain rightTerr;

			Terrain topTerr;

			Terrain bottomTerr;

			

			if(xCnt!=0)

			{left=true;}

			if(xCnt!=maxX-1)

			{right=true;}

			if(zCnt!=0)

			{bottom=true;}

			if(zCnt!=maxZ-1)

			{top=true;}

					

			if(left)

				{leftTerr=terrList[tileCnt-1].GetComponent<Terrain>();}

				else

					{leftTerr=null;}

			if(right)

				{rightTerr=terrList[tileCnt+1].GetComponent<Terrain>();}

				else

					{rightTerr=null;}

			if(top)

				{topTerr=terrList[tileCnt+maxX].GetComponent<Terrain>();}

				else

					{topTerr=null;}

			if(bottom)

				{bottomTerr=terrList[tileCnt-maxX].GetComponent<Terrain>();}

				else

					{bottomTerr=null;}

	//		Debug.Log("tileCnt  "+tileCnt+"     xCnt  "+xCnt+"    zCnt  "+zCnt);	

	//		Debug.Log("left  "+left+"     top  "+top+"    right  "+right+"    bottom  "+bottom);	

			terrList[tileCnt].GetComponent<Terrain>().SetNeighbors(leftTerr,topTerr,rightTerr,bottomTerr);	

			terrList[tileCnt].GetComponent<Terrain>().Flush();

				

			/////  update counters

			if(xCnt!=maxX-1)

			{

				xCnt++;

			}

			else

			{

				xCnt=0;

				zCnt++;

			}

		}



	}

	

	void FractalTerrains()

	{

	width = heightmapWidth*maxX;

	height = heightmapHeight*maxZ;

		

	float[,] grayArrayFull = new float[width,height];	

	float[,] grayArrayChunk = new float[heightmapWidth,heightmapHeight];

	terrNeighbours=new bool[maxX*maxZ,4];



	int tileNum=0;	

	int tileX=0;

	int tileZ=0;

	float roughness=50f;



	int zCnt=0;

	int xCnt=0;

		for (int tileCnt=0;tileCnt<maxX*maxZ;tileCnt++)

		{

			bool left=false;

			bool right=false;

			bool top=false;

			bool bottom=false;

			

			if(xCnt!=0)

			{left=true;}

			if(xCnt!=maxX-1)

			{right=true;}

			if(zCnt!=0)

			{bottom=true;}

			if(zCnt!=maxZ-1)

			{top=true;}

					

			terrNeighbours[tileCnt,0]=left;

			terrNeighbours[tileCnt,1]=top;

			terrNeighbours[tileCnt,2]=right;

			terrNeighbours[tileCnt,3]=bottom;

				

			/////  update counters

			if(xCnt!=maxX-1)

			{

				xCnt++;

			}

			else

			{

				xCnt=0;

				zCnt++;

			}

		}

		

		

		/////////////////Generate heights

		

		grayArrayFull=Generate(height,width,roughness); 

	

		int modX=0;

		int modZ=0;

		/////////	Apply the data 

		for (int tileCnt=0;tileCnt<maxX*maxZ;tileCnt++)

		{

			for (int z = 0;z<heightmapHeight;z++)

			{	

				for (int x = 0;x<heightmapWidth;x++)

				{

					if(x==0&&z==0)

					{

					modZ=(heightmapHeight*(tileX))-tileX;

					modX=(heightmapWidth*(tileZ))-tileZ;

					//Debug.Log(tileCnt+"    "+tileX+"    "+tileZ+"    "+modX+"    "+modZ);

					}

					grayArrayChunk[x,z]=grayArrayFull[x+modX,z+modZ];

				}

			}

			terrData[tileCnt].SetHeights(0,0,grayArrayChunk);

			terrList[tileCnt].GetComponent<Terrain>().Flush();

		

			//Set counters

			tileX++;

			if(tileX==maxX)

			{

				tileX=0;

				tileZ++;

			}

		

		}

		

	}

	

	float gRoughness;

	float gBigSize;





        public float[,] Generate(int iWidth, int iHeight, float iRoughness)

        {

            float c1, c2, c3, c4;

            float[,] points = new float[iWidth+1, iHeight+1];



            //Assign the four corners of the intial grid random color values

            //These will end up being the colors of the four corners

            c1 = UnityEngine.Random.value;

            c2 = UnityEngine.Random.value;

            c3 = UnityEngine.Random.value;

            c4 = UnityEngine.Random.value;

            gRoughness = iRoughness;

            gBigSize = iWidth + iHeight;

            DivideGrid(ref points, 0, 0, iWidth, iHeight, c1, c2, c3, c4);

            return points;

        }



        public void DivideGrid(ref float[,] points, float dX, float dY, float dwidth, float dheight, float c1, float c2, float c3, float c4)

        {

            float Edge1, Edge2, Edge3, Edge4, Middle;



            float newWidth = (float)Math.Floor(dwidth / 2);

            float newHeight = (float)Math.Floor(dheight / 2);



            if (dwidth > 1 || dheight > 1)

            {

                Middle = ((c1 + c2 + c3 + c4) / 4)+Displace(newWidth + newHeight);	//Randomly displace the midpoint!

                Edge1 = ((c1 + c2) / 2);	//Calculate the edges by averaging the two corners of each edge.

                Edge2 = ((c2 + c3) / 2);

                Edge3 = ((c3 + c4) / 2);

                Edge4 = ((c4 + c1) / 2);//

                //Make sure that the midpoint doesn't accidentally "randomly displaced" past the boundaries!

                Middle= Rectify(Middle);

                Edge1 = Rectify(Edge1);

                Edge2 = Rectify(Edge2);

                Edge3 = Rectify(Edge3);

                Edge4 = Rectify(Edge4);

                //Do the operation over again for each of the four new grids.

                DivideGrid(ref points, dX, dY, newWidth, newHeight, c1, Edge1, Middle, Edge4);

                DivideGrid(ref points, dX + newWidth, dY, dwidth - newWidth, newHeight, Edge1, c2, Edge2, Middle);

                DivideGrid(ref points, dX + newWidth, dY + newHeight, dwidth - newWidth, dheight - newHeight, Middle, Edge2, c3, Edge3);

                DivideGrid(ref points, dX, dY + newHeight, newWidth, dheight - newHeight, Edge4, Middle, Edge3, c4);

            }

            else	//This is the "base case," where each grid piece is less than the size of a pixel.

            {

                //The four corners of the grid piece will be averaged and drawn as a single pixel.

                float c = (c1 + c2 + c3 + c4) / 4;



                points[(int)(dX), (int)(dY)] = c;

                if (dwidth == 2)

                {

                    points[(int)(dX+1), (int)(dY)] = c;

                }

                if (dheight == 2)

                {

                    points[(int)(dX), (int)(dY+1)] = c;

                }

                if ((dwidth == 2) && (dheight == 2))

                {

                    points[(int)(dX + 1), (int)(dY+1)] = c;

                }

            }

        }

       private float Rectify(float iNum)

        {

            if (iNum < 0)

            {

                iNum = 0;

            }

            else if (iNum > 1.0)

            {

                iNum = 1.0f;

            }

            return iNum;

        }



        private float Displace(float SmallSize)

        {



            float Max = SmallSize/ gBigSize * gRoughness;

            return (float)(UnityEngine.Random.value - 0.5) * Max;

        }	

	

}