Perlin Noise Mess

Here lies my good perlin noise generator:

You see, Im trying to upscale my previous script that worked REALLY well!

However, I wanted to upscale it because the way it’s set up, it can only generate one “packet.”
My new script’s goal is to have it generate more packets and potentially with it some islands.

Old Script
--scripted by GreekForge
local pCont = workspace.PerlinContainer
local coms = game.ReplicatedStorage.RemoteEvent

local deb = true

local gridPart = Instance.new("Part")
gridPart.TopSurface = Enum.SurfaceType.Smooth
gridPart.Name = "GridPart"
gridPart.Anchored = true
gridPart.CanCollide = false
gridPart.Size = Vector3.new(5, 5, 5)

local function generateGrid(gridSize) --generates the grid :T
	--clean up section
	for _, p in pairs(pCont:GetChildren()) do
		p:Destroy()
	end
	
	wait()
	
	gridSize = math.clamp(gridSize, 5, 99) or 5
	gridSize = gridSize - 1
	for x=0, gridSize do
		wait()
		coroutine.wrap(function()
			local tX = x
			for y=0, gridSize do
				local nPar = gridPart:Clone()
				nPar.Position = Vector3.new(tX * gridPart.Size.X + gridPart.Size.X, 0, y * gridPart.Size.Z + gridPart.Size.Z)
				nPar.Parent = pCont
			end
		end)()
	end
end

local function perl(gridSize)
	deb = true
	generateGrid(gridSize)
	
	wait(0.5)
	local function rand(n)
		return math.random(-n, n)
	end
	local GradVects = { --yup, its a doozy
		Vector2.new(rand(500)/500, rand(500)/500).Unit,
		Vector2.new(rand(500)/500, rand(500)/500).Unit,
		Vector2.new(rand(500)/500, rand(500)/500).Unit,
		Vector2.new(rand(500)/500, rand(500)/500).Unit,
	}
	
	local cStones = { --corner stones or points...
		Vector2.new(0,0),
		Vector2.new(gridSize*gridPart.Size.X + gridPart.Size.X, 0),
		Vector2.new(0, gridSize*gridPart.Size.Z + gridPart.Size.Z),
		Vector2.new(gridSize*gridPart.Size.X + gridPart.Size.X, gridSize*gridPart.Size.Z + gridPart.Size.Z)
		}
	
	for _, p in pairs(pCont:GetChildren()) do
		wait()
		if p:IsA("Part") then
			local v2Pos = Vector2.new(p.Position.X, p.Position.Z)
			local dVectors = {
				(v2Pos - cStones[1]).Unit,
				(v2Pos - cStones[2]).Unit,
				(v2Pos - cStones[3]).Unit,
				(v2Pos - cStones[4]).Unit,
			}
			local dots = {
				dVectors[1]:Dot(GradVects[1]),
				dVectors[2]:Dot(GradVects[2]),
				dVectors[3]:Dot(GradVects[3]),
				dVectors[4]:Dot(GradVects[4]),
			}
			local ab = dots[1] + (v2Pos.X/(cStones[1] - cStones[2]).Magnitude)*(dots[2] - dots[1])
			local cd = dots[3] + (v2Pos.X/(cStones[3] - cStones[4]).Magnitude)*(dots[4] - dots[3])
			local finVal = ab + (v2Pos.Y/(cStones[1] - cStones[3]).Magnitude)*(cd - ab)
			p.Position = Vector3.new(v2Pos.X, finVal * gridPart.Size.Y * 1.75, v2Pos.Y)
			p.Color = Color3.new((finVal+1)/2, -((finVal+1)/2)*(((finVal+1)/2)+1), 1-((finVal+1)/2))
			--print(finVal)
		end
	end
	deb = false
end

math.randomseed(tick() + time()) --second step
wait(3)
perl(9)

coms.OnServerEvent:Connect(function(plr, mess)
	math.randomseed(tick() + time()) --second step
	if mess == "MakeNew" and not deb then
		perl(9)
	end
end)
New Script
--scripted by GreekForge
local pCont = workspace.PerlinContainer
local coms = game.ReplicatedStorage.RemoteEvent

local deb = true

local gridPart = Instance.new("Part")
gridPart.TopSurface = Enum.SurfaceType.Smooth
gridPart.Name = "GridPart"
gridPart.Anchored = true
gridPart.CanCollide = false
gridPart.Size = Vector3.new(5, 5, 5)

local function rand(n)
	return math.random(-n, n)
end

local function generate(pSize, gSize)
	deb = true
	--clean up section
	for _, p in pairs(pCont:GetChildren()) do
		p:Destroy()
	end
	
	--grid section
	gSize = math.clamp(gSize, pSize, pSize*100) or pSize
	if gSize % pSize ~= 0 then
		gSize = gSize + (gSize%pSize)
	end
	
	--packet and gridpart generation
	for i=1, gSize, pSize do
		wait()
		for j=1, gSize, pSize do
			wait()
			coroutine.wrap(function()
				local f = Instance.new("Folder")
				f.Name = "Packet"..(((i-1)/pSize)+1)..(((j-1)/pSize)+1)
				f.Parent = pCont
				
				local index1 = i
				local index2 = j
				for x=1, pSize do
					wait()
					coroutine.wrap(function()
						for z=1, pSize do
							wait()
							local c = gridPart:Clone()
							c.Position = Vector3.new((index1 * gridPart.Size.X ) + x * gridPart.Size.X, 0, (index2 * gridPart.Size.Z ) + z * gridPart.Size.Z)
							c.Parent = f
						end
					end)()
				end
			end)()
		end
	end
	
	local vectCont = Instance.new("Folder")
	vectCont.Name = "VectorContainer"
	vectCont.Parent = pCont
	
	local vectNum = math.sqrt(#pCont:GetChildren())
	for x=0, vectNum do
		coroutine.wrap(function()
			for z=0, vectNum do
				local Vect = Instance.new("Folder") --the Vector "object"
				Vect.Name = "Vector"..(x+1)..(z+1)
				Vect.Parent = vectCont
				
				local vVectX = Instance.new("NumberValue") --the vector x
				vVectX.Name = "VecX"
				vVectX.Value = math.clamp(rand(500)/500, -1, 1)
				vVectX.Parent = Vect
				local vVectZ = Instance.new("NumberValue") --the vector Z
				vVectZ.Name = "VecZ"
				vVectZ.Value = math.clamp(rand(500)/500, -1, 1)
				vVectZ.Parent = Vect
				local PosInfo = Instance.new("Vector3Value") --the positional vector
				PosInfo.Name = "Position"
				PosInfo.Value = Vector3.new(x*gridPart.Size.X*pSize, 0, z*gridPart.Size.Z*pSize)
				PosInfo.Parent = Vect
			end
		end)()
	end
end

local function perl()
	local partContainers = {}
	for _, fold in pairs(pCont:GetChildren()) do --basically got to remove vector container (Damn thats annoying)
		if fold.Name ~= "VectorContainer" then
			table.insert(partContainers, fold)
		end
	end
	for _, partFolder in pairs(partContainers) do --for every part folder in the container
		local CenterX = string.sub(partFolder.Name, #partFolder.Name-1, #partFolder.Name-1)
		local CenterZ = string.sub(partFolder.Name, #partFolder.Name, #partFolder.Name)
		local vecTable = {
			pCont.VectorContainer["Vector"..CenterX..CenterZ],
			pCont.VectorContainer["Vector"..(CenterX+1)..CenterZ],
			pCont.VectorContainer["Vector"..CenterX..(CenterZ+1)],
			pCont.VectorContainer["Vector"..(CenterX+1)..(CenterZ+1)],
		}
		for _, part in pairs(partFolder:GetChildren()) do --for every part in the part folder
			wait()
			local v2Pos = Vector2.new(part.Position.X, part.Position.Z) --why do i translate every thing to 2 dimension? idk
			
			local GradVects = { --god this is a lot
				Vector2.new(vecTable[1].VecX.Value, vecTable[1].VecZ.Value).Unit,
				Vector2.new(vecTable[2].VecX.Value, vecTable[2].VecZ.Value).Unit,
				Vector2.new(vecTable[3].VecX.Value, vecTable[3].VecZ.Value).Unit,
				Vector2.new(vecTable[4].VecX.Value, vecTable[4].VecZ.Value).Unit,
			}
			
			local cStones = { --corner stones or points...
				Vector2.new(vecTable[1].Position.Value.X, vecTable[1].Position.Value.Z),
				Vector2.new(vecTable[2].Position.Value.X, vecTable[2].Position.Value.Z),
				Vector2.new(vecTable[3].Position.Value.X, vecTable[3].Position.Value.Z),
				Vector2.new(vecTable[4].Position.Value.X, vecTable[4].Position.Value.Z),
			}
			
			local dVectors = {
				(v2Pos - cStones[1]).Unit,
				(v2Pos - cStones[2]).Unit,
				(v2Pos - cStones[3]).Unit,
				(v2Pos - cStones[4]).Unit,
			}
			local dots = {
				dVectors[1]:Dot(GradVects[1]),
				dVectors[2]:Dot(GradVects[2]),
				dVectors[3]:Dot(GradVects[3]),
				dVectors[4]:Dot(GradVects[4]),
			}

			local ab = dots[1] + (v2Pos.X/(cStones[1] - cStones[2]).Magnitude)*(dots[2] - dots[1])
			local cd = dots[3] + (v2Pos.X/(cStones[3] - cStones[4]).Magnitude)*(dots[4] - dots[3])
			local finVal = ab + (v2Pos.Y/(cStones[1] - cStones[3]).Magnitude)*(cd - ab)
			
			part.Position = Vector3.new(v2Pos.X, finVal * gridPart.Size.Y, v2Pos.Y)
			part.Color = Color3.new((finVal+1)/2, -((finVal+1)/2)*(((finVal+1)/2)+1), 1-((finVal+1)/2))
			
			print(finVal)
		end
	end
	deb = false
end

math.randomseed(tick() + time()) --second step
wait(1.5)
generate(3, 9)
wait(1.5)
perl()

deb = false

coms.OnServerEvent:Connect(function(plr, mess)
	math.randomseed(tick() + time()) --second step
	if mess == "MakeNew" and not deb then
		generate(3, 9)
		wait(1.5)
		perl()
	end
end)

As you can see, the only changes between them is the amount of packets, and therefore gradient vectors. Now I’m unsure if I’m correctly generating them, but my main issue seems to be from the interpolation step of the algorithm.

Snippet (cStones being the position of the gradient vector):

local ab = dots[1] + (v2Pos.X/(cStones[1] - cStones[2]).Magnitude)*(dots[2] - dots[1])
local cd = dots[3] + (v2Pos.X/(cStones[3] - cStones[4]).Magnitude)*(dots[4] - dots[3])
local finVal = ab + (v2Pos.Y/(cStones[1] - cStones[3]).Magnitude)*(cd - ab)

I think the Fractions I’m getting are incorrect, however I am unsure of what is really my issue.
I’ve basically copied the code exactly from the old script, so It might not even be my issue…

So I’ve tried to rewrite the code (which I got from this video) to this:

local abSlope = (dots[2] - dots[1])/(cStones[2].X - cStones[1].X)
local ab = abSlope * (v2Pos.X - cStones[1].X) + dots[1]
local cdSlope = (dots[4] - dots[3])/(cStones[4].X - cStones[3].X)
local cd = cdSlope * (v2Pos.X - cStones[3].X) + dots[3]
local finValSlope = (cd - ab)/(cStones[3].Y - cStones[1].Y)
local finVal = finValSlope * (cd - ab) + ab

But it didn’t really help :T
I’ve also noticed that moving the packet size to a larger number as well as grid size makes the generation a little better…
image

If anyone has any experience with this, or sees something glaring in my code, please give me some advice.
If you dont get my new system, just ask and I’ll explain it to you.
I’m gonna sleep now, its 1:07 AM here…

3 Likes

Why not just use math.noise()?
I’m using perlin noise for a project and I’ve been able to get away with using

local y = math.noise(x / xDensity, z / zDensity, seed) * amplitude
--xDensity and zDensity define how dense the perlin noise pattern is
--seed is any number that stays constant throughout the grid, but changes with each generation, I use math.random() for this
--amplitude is the height of the waves

I didn’t realize that math.noise applied to this. But I would rather avoid using it as it ruins the goal of the project which is to create the algorithm from scratch…

1 Like

Ah, I see. In that case, I recommend you take a look at Adrian Biagioli’s explanation and implementation of the algorithm. (It’s written in C# but you can probably get the general idea if you’re familiar with any non-scripting languages)

https://flafla2.github.io/2014/08/09/perlinnoise.html

Oh I already understand perlin noise, I just think I’ve messed up my implementation. My interpolation is probably root of the problem…

In fact, I’ve found an issue with my dot products, I’m getting a -nan(ind) value for the fourth dot product:

local dVectors = {
	(v2Pos - cStones[1]).Unit,
	(v2Pos - cStones[2]).Unit,
	(v2Pos - cStones[3]).Unit,
	(v2Pos - cStones[4]).Unit,
	}
local dots = {
	dVectors[1]:Dot(GradVects[1]),
	dVectors[2]:Dot(GradVects[2]),
	dVectors[3]:Dot(GradVects[3]),
	dVectors[4]:Dot(GradVects[4]),
	}
print(dots[4])

I’ve fixed my -nan(ind) issue which was due to block placement issues. But I still don’t get desirable results:
image

I really think my interpolation is wrong but I can’t find anything in it that could suggest its wrong…

What’s the reason for structuring the terrain in “packets”? Is this just for terrain generation or does it serve a purpose in your game?

How are you determining which gradient vectors to use? My initial suspicion would be that for each “chunk/packet” you are choosing unique vectors for corners that are shared between packets, which would lead to the discontinuities you see at the corners and edges. The code is a tad hard to understand, so this is just my suspicion

As an aside, I would suggest creating a module that deals with just generating noise values in a standard range (usually -1 to 1), then utilize that module in your terrain generation. Mixing the terrain generation code with code generating the noise will be hard to maintain in the future (speaking from experience)

1 Like

One source of NaNs can be trying to get the .Unit of a zero vector, which even your revised code still has as a possibility, though with very low probability. rand(500)/500 can be 0, and if it is zero for both X and Z components, Unit will NaN. It’s a 1-in-1001^2 chance, but in something called millions of times, you can expect to encounter something even with a roughly 1-in-a-million chance of happening.

1 Like

Actually, each grad vector is calculated immediately on generation.
The way they’re generated works by:

  1. determining the amount of packets
  2. Rooting that number and adding one to get each “corner” of each packet (so 3x3 packet size for a 9x9 grid size yields a 4x4 vector grid that covers the 9x9 grid)
    A. Basically, there is a singular vector for each corner of each packet, if the packet is next to another there will ONLY be one vector.

Weird explanation, I know, but that’s the simplest I can think of.

EDIT: As for vector choosing, I named each vector info folder an appropriate 2 digit number that corresponds to the packet they surround.
So Packet11 (the first packet) chooses Vector11, Vector12, Vector21, and Vector22. They correspond to each corner and currently the system works well.
Packet22 chooses Vector22, Vector23, Vector32, and Vector33. (22 is used as a sort of “anchor” vector and the others go as to the right, to the left, and to the diagonal of the anchor)
EDIT2: Packet 12 is technically the packet that follows 11 in the row, while 22 is in another column if it wasn’t clear

Well, currently I just want to expand on my previous Perlin script. There isn’t really a goal or purpose in this project other than understanding terrain generation and my own amusement.

The packet system is what I have thought up for getting multiple 2D perlins next to each other. I’m basically equating a packet to one perlin image, if that makes any sense.

EDIT: I’ve realized now, that you can basically call my packets, chunks.

1 Like

Ladies and Gentlemen, we have a success.
Now it’s not perfect, but I realize that It works as well as I want it to. As for my project, I deem it a success!

My issue was from the grid and gradient vector generation, as the vectors were too far and there were too many parts in each “packet” or chunk.

New Generation Code for those interested:

De Code
local function generate(pSize, gSize)
	deb = true
	--clean up section
	for _, p in pairs(pCont:GetChildren()) do
		p:Destroy()
	end
	
	--grid section
	gSize = math.clamp(gSize, pSize, pSize*100) or pSize
	if gSize % pSize ~= 0 then
		gSize = gSize + (gSize%pSize)
	end
	gSize = gSize -1
	
	--packet and gridpart generation
	for i=0, gSize, pSize do
		wait()
		coroutine.wrap(function()
			for j=0, gSize, pSize do
				wait()
				coroutine.wrap(function()
					local f = Instance.new("Folder")
					f.Name = "Packet"..(((i)/pSize)+1)..(((j)/pSize)+1)
					f.Parent = pCont
					
					local index1 = i
					local index2 = j
					for x=0, (pSize-1) do
						wait()
						coroutine.wrap(function()
							for z=0, (pSize-1) do
								local x1 = (index1 * gridPart.Size.X) + (x * gridPart.Size.X) + gridPart.Size.X/2
								local z1 = (index2 * gridPart.Size.Z) + (z * gridPart.Size.Z) + gridPart.Size.Z/2
								wait()
								local c = gridPart:Clone()
								c.Position = Vector3.new(x1, 0, z1)
								c.Parent = f
							end
						end)()
					end
				end)()
			end
		end)()
	end
	
	local vectCont = Instance.new("Folder")
	vectCont.Name = "VectorContainer"
	vectCont.Parent = pCont
	
	local vectNum = math.sqrt(#pCont:GetChildren())+2
	for x=0, vectNum do
		coroutine.wrap(function()
			for z=0, vectNum do
				local Vect = Instance.new("Folder") --the Vector "object"
				Vect.Name = "Vector"..(x+1)..(z+1)
				Vect.Parent = vectCont
				
				local pp = Vector2.new(rand(999)/999, rand(999)/999).Unit
				local vVectX = Instance.new("NumberValue") --the vector x
				vVectX.Name = "VecX"
				vVectX.Value = math.clamp(pp.X, -1, 1)
				vVectX.Parent = Vect
				local vVectZ = Instance.new("NumberValue") --the vector Z
				vVectZ.Name = "VecZ"
				vVectZ.Value = math.clamp(pp.Y, -1, 1)
				vVectZ.Parent = Vect
				local PosInfo = Instance.new("Vector3Value") --the positional vector
				PosInfo.Name = "Position"
				local x1 = x * gridPart.Size.X * pSize
				local z1 = z * gridPart.Size.Z * pSize 
				PosInfo.Value = Vector3.new(x1, 0, z1)
				PosInfo.Parent = Vect
			end
		end)()
	end
end

Images of this newfound success:


image

Yes, I’ve decided on a new color pallette.
You can test it for yourself here:

2 Likes

I know this is like necro-bumping the thread. But I created a public GitHub for the project for those who want to see what I did and/or add onto it. I have tried to make it Rojo compatible for those who use that!