How can I correctly offset seeded perlin noise over multiple terrains?

Hello,

I have been creating a randomly seeded world using a single terrain and perlin noise lately, which works as intended. However, I’m attempting to create a new terrain next to the original with an offset applied to the original seeded noise, which in theory should create a seamless tile next to the original but I’m having no luck.

I have a test project where I’d found the number 9.98 to be a working offset by trial and error however it’s not working for my current terrain(s).

Here’s a screenshot of the result I’m getting, the darker patches indicate areas below water.

Problematic terrain.

As you can see, it’s not right. The far-left terrain is the initial and is created with no offset and the seed, the right is created using the seed plus and offset of 9.98. Each terrain has a script attached to it, ready to set neighbours; the script also contains the offset information used upon world generation. See below.

Neighbour script.

Finally, each terrain is a child of a single object controlling world generation, the section relating to manipulating terrain data can be seen below.

`
IEnumerator CreateLandscape()
{
for (int i = 0; i < transform.childCount; i++)
{
TerrainData tData = transform.GetChild(i).GetComponent().terrainData;
Neighbour tNeighbour = transform.GetChild(i).GetComponent();
Vector2 tOffset = new Vector2(tNeighbour.OffsetX, tNeighbour.OffsetY);

		loadingMessage = string.Format("Terrain_{0}: {1}", i, "Creating terrain heights, using random seed.");
		yield return StartCoroutine(CreateTerrain(tData, tOffset));
		yield return new WaitForSeconds(0.5f);
		
		if (i == 0)
		{
			for (int w = 0; w < tData.heightmapWidth; w++)
			{
				for (int h = 0; h < tData.heightmapHeight; h++)
				{
					float cHeight = tData.GetHeight(w, h);				
					
					if (cHeight < terrainLowestElevation)
					{
						terrainLowestElevation = cHeight;
					}
				}
			}
			
			terrainWaterLevel = terrainLowestElevation + defaultWaterOffset;
		}
		
		loadingMessage = string.Format("Terrain_{0}: {1}", i, "Painting terrain based on global water level.");
		yield return StartCoroutine(PaintTerrain(tData));
		yield return new WaitForSeconds(0.5f);
		
		//yield return StartCoroutine(CreateFoliage(tData, transform.GetChild(i), i));
		//yield return new WaitForSeconds(0.5f);
		
		yield return new WaitForSeconds(0.5f);
	}
	
	loadingMessage = string.Format("Environment: {0}", "Setting terrain neighbours, and removing seams.");
	yield return StartCoroutine(SetTerrainNeighbours());
	
	loadingMessage = string.Format("Environment: {0}", "Positioning global water source.");
	TerrainData wData = GameObject.Find("Terrain_M").GetComponent<Terrain>().terrainData;
	yield return StartCoroutine(PositionWater(wData));
	
	yield return StartCoroutine(SetTerrainNeighbours());
	
	loadingMessage = string.Format("Environment: {0}", "Positioning the player.");
	yield return StartCoroutine(PositionPlayer());		
	yield return new WaitForSeconds(0.5f);
	
	hasLoaded = true;
	
	yield return null;
}

IEnumerator CreateTerrain(TerrainData tData, Vector2 tOffset)
{
	int Digit_1 = int.Parse(Seed.ToString()[0].ToString());
	int Digit_2 = int.Parse(Seed.ToString()[1].ToString());
	int Digit_3 = int.Parse(Seed.ToString()[2].ToString());
	int Digit_4 = int.Parse(Seed.ToString()[3].ToString());
	int Digit_5 = int.Parse(Seed.ToString()[4].ToString());
	
	float TileHeight = (Digit_1 < 5) ? defaultTerrainHeight + (Digit_1 * 2) : defaultTerrainHeight + Digit_1;
	float TileSize = (Digit_2 > 5) ? (float)Digit_2 : 5f;
	
	Vector3 TerrainSize = tData.size;
	TerrainSize.y = TileHeight;
	tData.size = TerrainSize;
	
	// Create a new empty array which will hold the generated terrain data.
	float[,] tHeights = new float[tData.heightmapWidth, tData.heightmapHeight];		
	
	for (int w = 0; w < tData.heightmapWidth; w++)
	{
		for (int h = 0; h < tData.heightmapHeight; h++)
		{
			tHeights[w, h] = Mathf.PerlinNoise((Seed + tOffset.y) + ((float)w / (float)tData.heightmapWidth) * TileSize, (Seed + tOffset.x) + ((float)h / (float)tData.heightmapHeight) * TileSize);
		}
	}
	
	// Apply the generated data to the terrainData.
	tData.SetHeights(0, 0, tHeights);
	
	// Terrain heights have been generated, return from this function.
	yield return null;
}

`

I have no issues with setting the neighbours or stitching terrains together as that is the final part of generating the world; before I can do so though, the noise needs to align properly.

So, to reiterate my question; Am I doing something wrong, is the offset of 9.98 just a coincidence in my text project? What would be the correct way to find the next offset to created a tiled perlin terrain?

Hopefully I’ve provided enough information, let me know if you need any more. Thanks, LD.

I have found the solution to this problem simply by trial and error; the new offset is simply 5.

However, I would like to know if there’s a better solution by calculating the offset if for instance the size of the terrain or resolution changes.

EDIT: The solution is that the offset is the TileSize used in the perlin function.