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;
}
}