How to detect the first layer of a randomly generated map?

I’m making a random map generator and i needed help with finding the first layer of my map (see below) to know which parts are grass, which are dirt, stone, etc.

I have tried to color the parts by their position on the Y axis but am not proud of the results.

Example of a random map:

Small example of what i want:

Script:

local MAP_SIZE_X   = 64
local MAP_SIZE_Y   = 32
local MAP_SIZE_Z   = 64
local SEED         = 0
local NOISE_SCALE  = 30
local AMPLITUDE    = 20
local BLOCK_SIZE   = 4

if workspace.SEED.Value == 0 then
	SEED = math.random(0, 1000000)
	workspace.SEED.Value = SEED
else
SEED = workspace.SEED.Value
end
for x = 0, MAP_SIZE_X do
for z = 0, MAP_SIZE_Z do
	for y = 0, MAP_SIZE_Y do
		local X_NOISE = math.noise(y/NOISE_SCALE, z/NOISE_SCALE, SEED) * AMPLITUDE
		local Y_NOISE = math.noise(x/NOISE_SCALE, z/NOISE_SCALE, SEED) * AMPLITUDE
		local Z_NOISE = math.noise(x/NOISE_SCALE, y/NOISE_SCALE, SEED) * AMPLITUDE
		local DENSITY = X_NOISE + Y_NOISE + Z_NOISE + y
		if DENSITY < 10 and DENSITY > 0 then
			local part = Instance.new("Part", workspace.TerrainFolder)
			part.Anchored = true
			part.Material = Enum.Material.SmoothPlastic
			part.Size = Vector3.new(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
			part.CFrame = CFrame.new(x*BLOCK_SIZE, y*BLOCK_SIZE, z*BLOCK_SIZE)
		end
	end
end
wait()
end

Thank you everyone!

1 Like

Some things you should note as well before I talk about my proposed solution…

  1. “Wait()” was deprecated recently…it is recommended that you use “task.wait()” instead.
  2. It’s also recommended that you don’t use Instance.new() with the parent argument. The linked topic explains why.

When I was testing out your map generation script…and I noticed…if a block is on top of another one, it will always be 4 studs above it.

You can create another loop that loops through all the parts inside the folder and use Ray Casting to check if there’s a part that’s 4 studs above it. If there’s not a part above it by 4 studs…the script will consider it a top layer.

In this script below…it does just that and for every part considered a top layer…it turns it green (plus is has the fixes I recommended before):

local MAP_SIZE_X   = 64
local MAP_SIZE_Y   = 32
local MAP_SIZE_Z   = 64
local SEED         = 0
local NOISE_SCALE  = 30
local AMPLITUDE    = 20
local BLOCK_SIZE   = 4

if workspace.SEED.Value == 0 then
	SEED = math.random(0, 1000000)
	workspace.SEED.Value = SEED
else
	SEED = workspace.SEED.Value
end
for x = 0, MAP_SIZE_X do
	for z = 0, MAP_SIZE_Z do
		for y = 0, MAP_SIZE_Y do
			local X_NOISE = math.noise(y/NOISE_SCALE, z/NOISE_SCALE, SEED) * AMPLITUDE
			local Y_NOISE = math.noise(x/NOISE_SCALE, z/NOISE_SCALE, SEED) * AMPLITUDE
			local Z_NOISE = math.noise(x/NOISE_SCALE, y/NOISE_SCALE, SEED) * AMPLITUDE
			local DENSITY = X_NOISE + Y_NOISE + Z_NOISE + y
			if DENSITY < 10 and DENSITY > 0 then
				local part = Instance.new("Part")
				part.Parent = workspace.TerrainFolder
				part.Anchored = true
				part.Material = Enum.Material.SmoothPlastic
				part.Size = Vector3.new(BLOCK_SIZE, BLOCK_SIZE, BLOCK_SIZE)
				part.CFrame = CFrame.new(x*BLOCK_SIZE, y*BLOCK_SIZE, z*BLOCK_SIZE)
			end
		end
	end
	task.wait()
end

for i, v in pairs(workspace.TerrainFolder:GetChildren()) do
	local MapPartParams = RaycastParams.new()
	MapPartParams.FilterDescendantsInstances = {v}
	MapPartParams.FilterType = Enum.RaycastFilterType.Blacklist
	local PartAbove = workspace:Raycast(v.CFrame.p,Vector3.new(0,4,0),MapPartParams)

	if not PartAbove then
		v.Color = Color3.new(0, 1, 0)
	else
		print("Layer detected above the part")
	end
	task.wait()
end

Hope this helps!

2 Likes

Can we still use wait(n)? I am really confused about this whole wait() thing

1 Like

Cache all the blocks in the table, then insert whatever arguments you want into each individual table, which can be accessed by providing the x, y and z coordinates:

local partCache = {}

partCache[x][z][y1] = Part
partCache[x][z][y2] = Part
partCache[x][z][y3] = Part

--// Using your code

for x = 0, MAP_SIZE_X do
	for z = 0, MAP_SIZE_Z do
		for y = 0, MAP_SIZE_Y do
			--code
			local part = Instance.new("Part")
			partCache[x][z][y] = part
			--code
		end
	end
end

Then you’d get the x and z coordinates of the positions and get the highest y value from the table:

for _, xTable in pairs(cache) do -- Looping through the part caches in each x coordinate
    for _, zTable in pairs(xTable) do -- Looping through the part xCache in each z coordinate
        local maxY = 0 -- Make sure this variable is smaller or equal to the hight minimum
        for y in pairs(zTable) do -- Looping through the zTable for each y coordinate
            if y > maxY then -- Checking if the y level of this part is bigger than the previous
                maxY = y -- Setting the y level
            end
        end
        zTable[y].Color = Color3.fromRGB(0, 0, 0) -- Setting the colour
    end
end
1 Like

You guys are amazing! I’ll check these solutions when I get back from work. Again thanks so much!

1 Like

Is there any way to speed up the terrain coloring process?

Not that I know off the top of my head unless you wanted to remove the task.wait() on the second loop, which will cause a lag spike.

I’ll look into that unless you wanna use Sqruitle’s method.

I can try his method for now but than you!

world generation going good!

edit: removing task.wait provides a nice old lag spike and doesn’t cover some of the land because of a script timeout.

1 Like