Incorrect "corrupted" Terrain LOD when introducing wait() between generating chunks

Using Procedural terrain generation code from Terrain Editor | Roblox Creator Documentation

LOD is corrupted when introducing a wait.
Written as “wait() – CORRUPTS LOD (Intended to prevent timeout when loading extra large map)” in the code below.



    local Players = game:GetService("Players")
     
    ------------------------------------------------------------------------------------------------------------------------------------------------
     
    local BASE_HEIGHT 		= 10				-- The main height factor for the terrain.
    local CHUNK_SCALE 		= 3 				-- The grid scale for terrain generation. Should be kept relatively low if used in real-time.
    local RENDER_DISTANCE 	= 120 / 4 			-- The length/width of chunks in voxels that should be around the player at all times
    local X_SCALE 			= 90 / 4			-- How much we should strech the X scale of the generation noise
    local Z_SCALE 			= 90 / 4			-- How much we should strech the Z scale of the generation noise
    local GENERATION_SEED	= math.random() 	-- Seed for determining the main height map of the terrain.
     
    ------------------------------------------------------------------------------------------------------------------------------------------------
     
    local chunks = {}
     
    local function chunkExists(chunkX, chunkZ)
    	if not chunks[chunkX] then
    		chunks[chunkX] = {}
    	end
    	return chunks[chunkX][chunkZ]
    end
     
    local function mountLayer(x, heightY, z, material)
    	local beginY = -BASE_HEIGHT
    	local endY = heightY
    	local cframe = CFrame.new(x * 4 + 2, (beginY + endY) * 4 / 2, z * 4 + 2)
    	local size = Vector3.new(4, (endY - beginY) * 4, 4)
    	workspace.Terrain:FillBlock(cframe, size, material)	
    end
     
    function makeChunk(chunkX, chunkZ)
    	local rootPosition = Vector3.new(chunkX * CHUNK_SCALE, 0, chunkZ * CHUNK_SCALE)
    	chunks[chunkX][chunkZ] = true -- Acknowledge the chunk's existance.
    	for x = 0, CHUNK_SCALE - 1 do
    		for z = 0, CHUNK_SCALE - 1 do
    			local cx = (chunkX * CHUNK_SCALE) + x
    			local cz = (chunkZ * CHUNK_SCALE) + z
    			local noise = math.noise(GENERATION_SEED, cx / X_SCALE, cz / Z_SCALE)
    			local cy = noise * BASE_HEIGHT
    			mountLayer(cx, cy, cz, Enum.Material.Grass)
    		end
    	end
    end
     
    function checkSurroundings(location)
    	local chunkX, chunkZ = math.floor(location.X / 4 / CHUNK_SCALE), math.floor(location.Z / 4 / CHUNK_SCALE)
    	local range = math.max(1, RENDER_DISTANCE / CHUNK_SCALE)
    	for x = -range, range do
    		for z = -range, range do
    			local cx, cz = chunkX + x
    			local cz = chunkZ + z
    			if not chunkExists(cx, cz) then
    				makeChunk(cx, cz)
                        wait() -- CORRUPTS LOD (Intended to prevent timeout when loading extra large map)
    			end
    		end
    	end
    end
     
    while true do
    	for _, player in pairs(Players:GetPlayers()) do
    		if player.Character then
    			local humanoidRootPart = player.Character:FindFirstChild("HumanoidRootPart")
    			if humanoidRootPart then
    				checkSurroundings(humanoidRootPart.Position)
    			end
    		end
    	end
    	wait(1)
    end


Examples:

With wait(.1), plain:

With wait(), plain:

Without wait; correct LOD:


This persists with different specifications:

Without wait, steep:

image

With wait:

image


Repro file: Repro.rbxl (19.8 KB)

1 Like

Thanks for the report! We’ve filed this internally and we’ll follow up here when we have an update for you.

4 Likes

This is fixed internally now. Fix will be shipped in one of the next releases.

Thanks for reporting this!

4 Likes

Not sure if this fix has already been pushed but this is still reproducible, I have a similar issue initially reported here and it was recommended by PeZsmistic that I post the issue here.

Not only does this happen when you yield the thread but if there are calls to this method when iterating through regions the rendering also breaks down (I’m assuming as it spills over into the next stack?).

Image:
image

File (not necessary, just empty baseplate with a script containing the code below):
reproducible_terrain_issue.rbxl (21.3 KB)

Code:

local points = [[
1	-164.499985, 14.2499866, 134
2	-144.499985, 14.2499866, 134
3	-136.499985, 14.2499857, 158
4	-156.499985, 14.2499847, 170
5	-168.499985, 14.2499857, 158
6	-156.499985, 14.2499857, 154
]]

local vertices = { }
for index, vertex in points:gmatch('(%d\t)([^\n]+)') do
	vertices[tonumber(index)] = Vector3.new(unpack(string.split(vertex, ', ')))
end

--[[
	Function: contourBounding
	Args: polygon (table), with vertices (vector3)
	Returns: (table) containing width, height and min/max boundaries of a region3
]]
local function contourBounding(poly)
	local maxX, minX = poly[1].x, poly[1].x
	local maxZ, minZ = poly[1].z, poly[1].z
	for i, v in ipairs(poly) do
		if v.x > maxX then
			maxX = v.x
		end
		if v.x < minX then
			minX = v.x
		end
		if v.z > maxZ then
			maxZ = v.z
		end
		if v.z < minZ then
			minZ = v.z
		end
	end

	return {
		w = maxX - minX;
		h = maxZ - minZ;
		p1 = Vector3.new(minX, poly[1].y - 11, minZ);
		p2 = Vector3.new(maxX, poly[1].y - 2, maxZ);
	}
end

--[[
	Function: getRegion
	Args: polygon (table), with vertices (vector3)
	Returns: min/max region3 of the given polygon
]]
local function getRegion3(poly)
	local bounds = contourBounding(poly)
	return Region3.new(bounds.p1, bounds.p2)
end


-- Here's where the issue begins
-- if you wait for one frame after filling the region, it produces incorrectly rendered (faces are inverted) water cells
local region = getRegion3(vertices):ExpandToGrid(4)
game.Workspace.Terrain:FillRegion(region, 4, 1792)
wait()
game.Workspace.Terrain:FillRegion(region, 4, Enum.Material.Water)


wait(10)

-- However, if we call it without yielding, we produce correct results:
local region = getRegion3(vertices):ExpandToGrid(4)
game.Workspace.Terrain:FillRegion(region, 4, 1792)
game.Workspace.Terrain:FillRegion(region, 4, Enum.Material.Water)
1 Like

Again, not sure if you’ve pushed the fix in the recent updates, but I can confirm this is still an issue:

robloxapp-20201124-1516269.wmv (3.3 MB)

1 Like

Something similar is happening to me, but much more severe. I am using RunService.Heartbeat:Wait() instead of wait().



image

The strange thing is that this occurs only on the first time Terrain:FillCylinder() is used. This bug happens in the first corner (where FillCylinder is run the first time) that is generated. This occurs 100% of the time (with my procedural generator).

2 Likes

It should be fixed now. Please let me know if your issues are fixed.

1 Like