3d perlin noise code takes ages to generate

I’m trying to make an island system using 3d Perlin noise, but my current code is extremely slow. At the time, I made it able to detect parts that are occluded, but only after generating every coordinate, and the code that checks it is also pretty slow. I tried using RunService.Heartbeat:Wait() rather than task.wait() to make it faster but it’s still obnoxiously slow and laggy.
Here’s a quick recording of how laggy it is

What it generates (the front islands are carved because i didnt want to wait a decade for the whole thing to generate, since with each row it gets slower)

The code (please forgive me for how poorly coded it is, I did pretty quickly and I'm pretty tired)
local blockSize = 4
local worldSize = 256
local divisor = 200 -- This makes the terrain more wild / crazy, and the higher it is the more overhangs there will be, and the less this is the less overhangs there will be.
local seed = math.random(1,1000)

local positions = {}

function get_update(position)
	local coord_to_place = Vector3.new(0,0,0)
end

function place_block(position:Vector3)
	local block = Instance.new("Part")
	block.Parent = workspace
	block.Size = Vector3.new(blockSize, blockSize, blockSize)
	block.Anchored = true
	block.CFrame = CFrame.new(position.X, position.Y, position.Z)
end

function check_for_air_exposure()
	for i,v:Vector3 in pairs(positions) do
		local is_valid = false
		local positions_to_check = {}
		local obstructed = {}
		
		-- sorry for this bad code lol i was too braindead while making this
		table.insert(positions_to_check, Vector3.new(v.X - blockSize, v.Y, v.Z))
		table.insert(positions_to_check, Vector3.new(v.X + blockSize, v.Y, v.Z))
		
		table.insert(positions_to_check, Vector3.new(v.X, v.Y - blockSize, v.Z))
		table.insert(positions_to_check, Vector3.new(v.X, v.Y + blockSize, v.Z))
		
		table.insert(positions_to_check, Vector3.new(v.X, v.Y, v.Z - blockSize))
		table.insert(positions_to_check, Vector3.new(v.X, v.Y, v.Z + blockSize))
		
		for _, pos in positions_to_check do
			--print(table.find(positions, pos))
			if table.find(positions, pos) then
				--print("Side obstructed")
				table.insert(obstructed, pos)
			else
				is_valid = true
			end
		end
		
		if is_valid then
			place_block(v)
		end
		
		if i % 100 == 0 then
			game["Run Service"].Heartbeat:Wait()
		end
	end
end

for x = 1, worldSize do
	for y = 1, worldSize do
		for z = 1, worldSize do
			local noiseValue = math.noise(
				(x + seed) * (blockSize/10) / 16, 
				(y + seed) * (blockSize/10) / 16, 
				(z + seed) * (blockSize/10) / 16) - ((y - (worldSize / 2)) / divisor
			) -- Subtract the world size divided by two because without it, the terrain will have alot of holes in the ground.
			
			if noiseValue >= .9 then
				table.insert(positions,Vector3.new(x*blockSize,y*blockSize,z*blockSize))
			end
		end
	end
	game["Run Service"].Heartbeat:Wait()
end

check_for_air_exposure()


I wanna be able to make it render much faster and if possible maybe in chunks, but I am very new to noise generation
Any help is appreciated!

2 Likes

LuaU is built off Lua 5.1, of which is a procedural language - sequential execution. Code is executed in order, this is shifted when (I’d not recommend) using coroutines or creating new threads to allow the simultaneous execution of multiple instances of your generation logic. To deal with your speed issue differently (this way I’d recommend), is by optimizing your code, a lot. But on a serious note what I mean is by using actors which should allow you to execute your code with a form of multi-threading. Scripts run using the CPU, don’t expect some blazing fast 2 ms loading times. I HIGHLY recommend to fix this you look into actors, workers and threads; these are COMMONLY used for voxel/node based map generation.

2 Likes

How about instead of putting a task.wait() between each generated Part loop through and do 10 or 20, then wait, and repeat.
You could then adjust how many Parts it loops through to try to control the delay or lag.

That is what i did in the check for air exposure function, altho o could reduce it a little

I think what I’m gonna do is change the noise logic to generate multiple tables of chunk data so i can do that instead of one single table with all the positions

1 Like

I like this approach, you mean so it’s like separated into multiple chunks?

Exactly; not only would this help with what you suggested, it would overall help to make the generation more efficient

1 Like

Yep them do take along time, unless it’s on the client … a bit faster that way.
Toss up something to show to take up time. Loading, Generating …

1 Like

Yea i will, i’ll add text like “generating terrain” “generating biomes” and “loading chunks” because I know that it’s gonna take a little for everything to load

Now that I think of it, since chunks will be made in new tables, won’t i have to make a function that can check inside every table? since I don’t think table.find checks tables inside of tables
(nevermind gave up on that approach)

Nevermind I’ve made zero progress, I have no idea how I could separate the generation in chunks, I tried separating with more for loops but then it just generated one chunk and then tried going to the function that checks which blocks to place and separating it all in multiple threads but then it only loads part of the map.
I’m extremely inexperienced in this domain so does anyone have an idea on how I could separate the generation into multiple chunks? I’m as lost as can be

With your positions table, you are looping through potentially thousands of vectors just for one block. Use a 3d table instead, no looping is required and it can be expanded later to use chunks. It works by creating tables inside tables. Heres how you can create it.

local grid = {}
for x = 1, 50 do -- any size can work, they all dont have to be equal for every axis either
	grid[x] = {}
	for y = 1, 50 do
		grid[x][y] = {}
		for z = 1,50 do 
			local block = 0 -- perlin noise code
			grid[x][y][z] = block
		end
	end
end

local block = grid[1][1][1] -- getting the first block, for example
for x = 1,50 do 
	for y = 1,50 do 
		for z = 1,50 do 
			-- make blocks
		end
	end
end
-- you can loop through it again to make blocks 
-- you can also occlude blocks that are surrounded by solid blocks by just checking the grid
-- watch out for blocks that are either larger or smaller in index than the grid allows
2 Likes

Thank you so much my code went from taking minutes and being really laggy to loading in 10 seconds without ANY lag, even when I allow for more terrain to spawn!

I don’t know if I should still ask this here or if I should make a new topic but can you also guide me on how I could generate this in chunks? I’m having trouble figuring it out
Edit: nevermind figured it out, have a nice day/night!

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.