Gaps Appearing in Randomly Generated Terrain (Visible blocks only)

Hello! I have been recently working on a random terrain generator through perlin noise, and cannot figure out why some blocks are missing when I generate the terrain with visible blocks (blocks exposed to air) only. I have tried checking every block surrounding a solid block, and if it is air, then show the block. I just do not understand how some parts are left out.

What it looks like:


As you can see, the terrain generates absolutely fine, however, there are small holes throughout it.

Part of modulescript that detects surrounding air blocks/inserts solid blocks:

function GetBlockFromPosition(WorldBlocks,X,Y,Z)
	local ReturnValue = nil
	local CalculatedChunk = {X = math.floor(X/16),Y = math.floor(Y/16),Z = math.floor(Z/16)}
	local ChunkBlocks = WorldBlocks["Chunk"..CalculatedChunk.X..CalculatedChunk.Y..CalculatedChunk.Z]
	if ChunkBlocks then
		if ChunkBlocks["Block"..X..Y..Z] then
			ReturnValue = ChunkBlocks["Block"..X..Y..Z]
		end
	end
	return ReturnValue
end

function module.CheckVisibleLayers(WorldBlocks,ChunkBlocks)
	for i,Block in pairs(ChunkBlocks) do
		if typeof(Block) ~= "number" then
			if Block.Name ~= "Air" then
				for x = 1,3 do
					for z = 1,3 do
						for y = 1,3 do
							local TargetBlock = GetBlockFromPosition(WorldBlocks,Block.X + (x - 2),Block.Y + (y - 2),Block.Z + (z - 2))
							if TargetBlock then
								if TargetBlock.Name == "Air" then
									ChunkBlocks[i].Visible = true
								end
							end
						end
					end
				end
			end
		end
	end
	return ChunkBlocks
end

function module.CreateBlocks(ChunkBlocks)
	local ChunkFolder = workspace.Chunks:FindFirstChild("Chunk"..ChunkBlocks.X..ChunkBlocks.Y..ChunkBlocks.Z)
	for i,Block in pairs(ChunkBlocks) do
		if typeof(Block) ~= "number" then
			if Block.Visible then
				local NewBlock = game.ServerStorage.Blocks:FindFirstChild(Block.Name)
				if NewBlock then
					NewBlock = NewBlock:Clone()
					NewBlock.Parent = game.Workspace.Chunks["Chunk"..Block.ChunkX..Block.ChunkY..Block.ChunkZ]
					NewBlock:SetPrimaryPartCFrame(CFrame.new(Block.X*BlockSize,Block.Y*BlockSize,Block.Z*BlockSize))
				end
			end
		end
	end
end

If you have any method ideas that I could use to fix this, please let me know, thank you!

Also, I can answer any other questions that anyone has on this to better understand how to fix it.

I’ve done some perlin noise worlds before so I have experience with this

Since all of your blocks are the same size, why would you be creating “air” blocks and save them in a table?
Using perlin noise for cubes, you technically don’t need to check if the other blocks around it exist before creating the block. Technically, in your map, you could have it iterate though x and z. Perlin noise will give you the altitude of the terrain block, or in the case of caves, it will tell you if a block should exist at that location or not
Since your terrain is made out of mountains, you can simply get the altitude at each X/Z coordinates by iterating them, and fill everything located below, regardless of if there would be blocks next to it, as that check wouldnt matter

Edit: If you don’t want to fill it to bottom however, you are better off checking the altitude of the blocks adjacent to it only, to know how low the block must go to fill. You technically don’t need to check the Y layers if you get the highest altitude from X and Z coordinates of a block
The block with lowest altitude adjacent to that coordinate will tell you how low it must go to fill it

2 Likes

This actually sounds like a much better solution, I’ll try it out and see how it goes!

No problem. Also little tip in case you didn’t know about that, just do help you make your terrain look more natural and realistic. If you stack multiple layers of perlin noise on top of eachothers(lets say 3 layers) and each layers use a different seed and each layers are actually having a different weight(let’s say layer1x1+layer2x.2+layer3x.05), you can archive much cooler looking mountains, as the differently weighted layers of perlin noise will allow for an overall mountain shape, will giving it small bumps so it looks more rocky and random

2 Likes

Thank you! This method worked much better than the original one, and it causes less lag. Sadly, I can’t do much about the chunks surrounding themselves, since blocks are chunk-based, however everything that is exposed to an open area is there.

This is what it looks like now:


By the way, those bottom-filled looking areas are all hallow inside.

I’ll try this method out and see how it goes, it seems interesting since with a single perlin noise generation, you do only get a wavy terrain, and random is a lot better and more interesting.

Oh that’s cool
You could however also remove the exterior from going to bottom by making it check the altitude on the next chunk if it is at the border of a chunk

Yes. There are YouTube videos explaining how stacking perlin noises with different weights affects the natural ruffness of the terrain. You may look that up if you want, it could be helpful.

Edit:
This imagine also describes it well

I don’t have any idea why this part of the script isn’t working in checking blocks from other chunks, even if I am sure everything here is right. The WorldBlocks dictionary has the chunk dictionaries, which are called Chunk..X..Y..Z, in which the X, Y, and Z values are the chunk’s position (16x16 in blocks). In the chunk dictionary, there are blocks which are also uniquely called by position (Block..X..Y..Z). This position is the position of the block (1x1). I am just confused how this part is not working, and if I did something wrong.

Code:

function GetBlockFromPosition(WorldBlocks,X,Y,Z)
	local ReturnValue = nil
	local CalculatedChunk = {X = math.floor(X/16),Y = math.floor(Y/16),Z = math.floor(Z/16)}
	local ChunkBlocks = WorldBlocks["Chunk"..CalculatedChunk.X..CalculatedChunk.Y..CalculatedChunk.Z]
	if ChunkBlocks then
		if ChunkBlocks["Block"..X..Y..Z] then
			ReturnValue = ChunkBlocks["Block"..X..Y..Z]
		end
	end
	return ReturnValue
end

Since you don’t have caves in your generation, you should save chunks as only X and Z
I suggest you have the chunks saved under just X and Z

Well, I will be adding caves into the generation at some point, since those seem interesting to try and generate, would I still not include Y chunk saving?

If you had the chunks as something like this:
-worldblocks
—X,Z chunks as /16(so like chunk who’s corner is at 32,64 would be chunk 2,4
—-X,Z blocks relative to chunk, so like block 35,67 would be block 3,3 of chunk 2,4
—-block value should contain Y altitude
Let’s say a block is 0,4 of chunk 3,4, then it should check the Y of block 16,4 of chunk 2,4

This should work

Oh. I though you wouldn’t have caves. In this case, that changes some things to how the generation I initially suggested should work.
Some changes to mention to what I suggested earlier:

  • I suggest you add a perlin noises cave generation below the perlin noises of mountains, it will look better
    -since I suggested to ended as far as lowest altitude was, this actually won’t work with caves
    With caves, you should instead be looking for vertical segments. Let’s say that mountain is at Y=120 highest. It should first for ant lowest of the first cave below. You could do that by, let’s say you got a cave who’s roof at an XZ location is Y=60, then now you will need it to go from 120 all way down to 60, or, instead, you can go make it from 120 to let’s say, Y=80 if 80 is lowest adjacent top surface. Then have one start from Y=60 yo let’s say, Y=70, where a highest adjacent cave ceiling is

In the case however of adjacent chunks, it shouldn’t matter if it’s a cave part of mountain part. You should be doing the same: look for lowest adjacent floor or ceiling. Note that for the ceilings, the adjacent ceiling may be lower than the ceiling of that coordinate. This is why you must look for both lower at ceiling and lowest roof adjacent. Let’s say yours has a cave at 180Y, adjacent has a part that starts at 180Y all way down to 170Y, you should end at 180Y. You should always end at the higuest ceiling found on the XZ located below the floor of that segment of that coordinate and if not, lowest floor that is adjacent and that is lower than the floor of that coordinate. When the point at wich a Pilar ends is found, repeat by getting higuest floor of a cave below that is lower than the ceiling of that Pilar, and repeat until you hit bottom

Since you plan to add caves, yes you need to save the Y of the higuest of a segment at each XZ, along with the Y of the lowest of that segment. Giving you vertical segments for each caves stacked on each other along an XZ pillar. However, chunks may keep only XZ

Alright, thanks for the info on that. One other question, when I attempt to generate chunks on a 0 axis (x or z), there are always holes that appear in the map along those chunks. Not too sure how this happens, unless this is just a bugged feature of math.noise.

It depends how you implemented it. Did you have your perlin noise or something multiply by 0?

In the code to generate the perlin noise, I do not have any multiplication in it. This is what it looks like:

function GenerateNoise(x,y,z,ChunkMultiply,NoiseScale,Amplitude,Seed)
	local XNoise = math.noise((y + ChunkMultiply.y)/NoiseScale,(z + ChunkMultiply.z)/NoiseScale,Seed)*Amplitude
	local YNoise = math.noise((x + ChunkMultiply.x)/NoiseScale,(z + ChunkMultiply.z)/NoiseScale,Seed)*Amplitude
	local ZNoise = math.noise((x + ChunkMultiply.x)/NoiseScale,(y + ChunkMultiply.y)/NoiseScale,Seed)*Amplitude
	local Density = XNoise + YNoise + ZNoise + (y+ChunkMultiply.y)
	return Density
end

(Small note: The code is based off of a tutorial on YouTube, so I may not 100% understand everything if something was wrong here)

From what I remember, having 0 as coordinate shouldn’t affect it.

But I didn’t do perlin noise cave generation for a game since about 2 years so I’m a bit rusty on it. Although I do remember that coordinates 0 was not causing issues in cave generations I’ve made

Can you try to do a print of all the parameters from that function aswell as the returned density? It might help me see why.

I assume you are using if density < a certain ammount then = air

This is what it looks like if I attempt to generate at 0,0,0 in the world:

The output seemed to have a steady pattern, however, suddenly an exact number appeared: image

(The format is XNoise,YNoise,ZNoise, and Density)

That’s weird. Other chunks generate normally?