Making Okeanskiy trash generation seed-based

(continued from this topic)

  1. What do you want to achieve?
    Last time from tinkering with my modified version of Okeanskiy’s Terrain Generation, SquarePapyrus12 suggested,

if the generation is different on all clients and you don’t want that…then make a seed system and all clients should have the same seed

Taking the advice into consideration, I began to tinker with the terrain generation a little more, so that the trash generation is consistent for all clients.
  1. What is the issue?
    However, the attempts I made to utilize seeds in overhauling the generation resulted in… this:


    Expectation

    Reality

  2. What solutions have you tried so far?
    From last topic, I added a NumberValue into the Workspace that denotes the seed for the trash.

SEED = game.Workspace.SEED				--	Get the IntValue in the Workspace.
SEED.Value = math.random(1000000)		--	Make its value a random number between 1 and a million.

TRASH_SEED = game.Workspace.TRASH_SEED	--	Get the NumberValue in the Workspace.
TRASH_SEED.Value = SEED.Value + 0.01	--	Make its value the SEED's value, plus a hundredth.

--	This counts how many unique trash types are there in the game.
t = game.ReplicatedStorage.Garbage:GetChildren()
print("Trash types: "..#t)

Afterwards, I modified the addTrash function in the Chunk Module.

local function addTrash(chunk)
	local posGrid = chunk.positionGrid
	local instances = chunk.instances
	local chunkPosX = chunk.x
	local chunkPosZ = chunk.z
	
	for x = 0, X-1 do
		for z = 0, Z-1 do
			local pos = posGrid[x][z]
			
			if pos.Y >= MIN_TRASH_SPAWN_HEIGHT and pos.Y <= MAX_TRASH_SPAWN_HEIGHT then
				
				math.randomseed((x * (chunkPosX+SEED))+(z * (chunkPosZ+SEED)))
				if math.random() < TRASH_DENSITY then
					local p = math.noise(TRASH_SEED) + 0.25
					local garbage = game.ReplicatedStorage.Garbage:GetChildren()
					local q = (p/0.75) * #garbage
					local r = math.ceil(q+1)
					if not r then
						r = #garbage
					elseif r then
						continue
					end
					local trash = garbage[r]:Clone()
					
					local cframe = CFrame.new(pos)
						* CFrame.new(
							math.random()*math.random(-10,10),
							0,
							math.random()*math.random(-10,10)
						)
						* CFrame.Angles(
							math.rad(math.random()*math.random(-30,30)),
							2*math.pi*math.random(),
							math.rad(math.random()*math.random(-30,30))
						)
					
					trash:SetPrimaryPartCFrame(cframe)
					trash.Parent = TrashModels
					
					table.insert(instances, trash)
					TRASH_SEED += 0.01
				end
			end
		end
	end
end

Running the game, I ended up with a landscape devoid of any trash. Just by looking at the script, we both see that I am doing something wrong. What am I missing?

2 Likes

You don’t generally don’t call randomseed every time you want to get a new number. You usually call it once at the beginning of the overall process of generating e.g. an entire level.

Another issue is that you always call it with the same number (well actually multiple numbers because of the above problem), causing you to always get the same sequence of random numbers, so the level is always the same. If the only thing you want is a different level each time, just put this at the top of your script:

math.randomseed(os.time())

math.random isn’t the best, you should look into using the “new” Random type, just look it up on the wiki.

The old trash generation did use math.randomseed((x * (chunkPosX+SEED)+(z * (chunkPosZ+SEED))), as it is based off of Okeanskiy’s Terrain Generation. Here is the old script:

local TERRAIN_HEIGHT_COLORS	= {
	[-50] = Color3.fromRGB(169,88,33);	-- Dark orange-ish
	[-10] = Color3.fromRGB(197,140,69);	-- Orange-ish
	[0] = Color3.fromRGB(212,173,97);	-- Yellow orange-ish
	[75] = Color3.fromRGB(223,210,158)	-- Pale yellowish
}	
local X, Z = 4,4
local WIDTH_SCALE = 16
local HEIGHT_SCALE = 100
local TERRAIN_SMOOTHNESS = 20
local MIN_TRASH_SPAWN_HEIGHT = -15
local MAX_TRASH_SPAWN_HEIGHT = 30
local TRASH_DENSITY = 0.5				-- Number between 0 and 1
local SEED = game.Workspace.SEED.Value

local TerrainModel = game.Workspace.TerrainModel
local TrashModels = game.Workspace.TrashModels
local Terrain = game.Workspace.Terrain

local wedge = Instance.new("WedgePart")
wedge.Anchored = true
wedge.TopSurface = Enum.SurfaceType.Smooth
wedge.BottomSurface = Enum.SurfaceType.Smooth

local function draw3DTriangle(a, b, c)
	local ab,ac,bc = b-a, c-a, c-b
	local abd,acd,bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)

	if (abd > acd) and (abd > bcd) then
		c,a = a,c
	elseif (acd > bcd) and (acd > abd) then
		a,b = b,a
	end

	ab,ac,bc = b-a, c-a, c-b

	local right = ac:Cross(ab).unit
	local up = bc:Cross(right).unit
	local back = bc.unit

	local height = math.abs(ab:Dot(up))

	local w1 = wedge:Clone()
	w1.Size = Vector3.new(0,height,math.abs(ab:Dot(back)))
	w1.CFrame = CFrame.fromMatrix((a+b)/2, right, up, back)
	w1.Parent = TerrainModel

	local w2 = wedge:Clone()
	w2.Size = Vector3.new(0,height,math.abs(ac:Dot(back)))
	w2.CFrame = CFrame.fromMatrix((a+c)/2, -(right), up, -(back))
	w2.Parent = TerrainModel

	return w1, w2
end

local function getHeight(chunkPosX, chunkPosZ, x, z)
	local height = math.noise(
		(X/TERRAIN_SMOOTHNESS * chunkPosX) + x/TERRAIN_SMOOTHNESS, 
		(Z/TERRAIN_SMOOTHNESS * chunkPosZ) + z/TERRAIN_SMOOTHNESS,
		SEED
	) * HEIGHT_SCALE
	
	if height > 20 then
		local difference = height - 20
		height += (difference * 1.2)
	end
	
	if height < -20 then
		local difference = height - -20
		height += (difference * 1.2)
	end
	
	return height
end


local function getPosition(chunkPosX, chunkPosZ, x, z)
	return Vector3.new(
		(chunkPosX*X*WIDTH_SCALE) + x*WIDTH_SCALE,
		getHeight(chunkPosX, chunkPosZ, x, z),
		(chunkPosZ*Z*WIDTH_SCALE) + z*WIDTH_SCALE
	)
end

local function paintWedge(wedge)
	local wedgeHeight = wedge.Position.Y
	
	local color
	local lowerColorHeight
	local higherColorHeight
	
	for height,heightColor in pairs(TERRAIN_HEIGHT_COLORS) do
		if wedgeHeight == height then
			color = heightColor
			break
		end
		
		if (wedgeHeight < height) and (not higherColorHeight or height < higherColorHeight) then
			higherColorHeight = height
		end
		
		if (wedgeHeight > height) and (not lowerColorHeight or height > lowerColorHeight) then
			lowerColorHeight = height
		end
	end
	
	if not color then
		if higherColorHeight == nil then
			color = TERRAIN_HEIGHT_COLORS[lowerColorHeight]
		elseif lowerColorHeight == nil then
			color = TERRAIN_HEIGHT_COLORS[higherColorHeight]
		else
			local alpha = (wedgeHeight - lowerColorHeight) / (higherColorHeight - lowerColorHeight)
			local lowerColor = TERRAIN_HEIGHT_COLORS[lowerColorHeight]
			local higherColor = TERRAIN_HEIGHT_COLORS[higherColorHeight]
			
			color = lowerColor:lerp(higherColor, alpha)
		end
	end
	
	wedge.Material = Enum.Material.Sand
	wedge.Color = color
end

local function addWater(chunk)
	local cframe = CFrame.new(
		(chunk.x + 0.5) * (chunk.WIDTH_SIZE_X),
		-70,
		(chunk.z + 0.5) * (chunk.WIDTH_SIZE_Z)
	)
	
	local size = Vector3.new(
		chunk.WIDTH_SIZE_X,
		100,
		chunk.WIDTH_SIZE_Z
	)
	
	game.Workspace.Terrain:FillBlock(cframe, size, Enum.Material.Water)
	
	chunk.waterCFrame = cframe
	chunk.waterSize = size
end

local function addTrash(chunk)
	local posGrid = chunk.positionGrid
	local instances = chunk.instances
	local chunkPosX = chunk.x
	local chunkPosZ = chunk.z
	
	for x = 0, X-1 do
		for z = 0, Z-1 do
			local pos = posGrid[x][z]
			
			if pos.Y >= MIN_TRASH_SPAWN_HEIGHT and pos.Y <= MAX_TRASH_SPAWN_HEIGHT then
				
				math.randomseed((x * (chunkPosX+SEED))+(z * (chunkPosZ+SEED)))
				if math.random() < TRASH_DENSITY then
					local garbage = game.ReplicatedStorage.Garbage:GetChildren()
					local trash = garbage[math.random(1,#garbage)]:Clone()
					
					local cframe = CFrame.new(pos)
						* CFrame.new(
							math.random()*math.random(-10,10),
							0,
							math.random()*math.random(-10,10)
						)
						* CFrame.Angles(
							math.rad(math.random()*math.random(-30,30)),
							2*math.pi*math.random(),
							math.rad(math.random()*math.random(-30,30))
						)
					
					trash:SetPrimaryPartCFrame(cframe)
					trash.Parent = TrashModels
					
					table.insert(instances, trash)
				end
				
			end
		end
	end
end

local Chunk = {}
Chunk.__index = Chunk

Chunk.WIDTH_SIZE_X = X * WIDTH_SCALE
Chunk.WIDTH_SIZE_Z = Z * WIDTH_SCALE

function Chunk.new(chunkPosX, chunkPosZ)
	local chunk = {
		instances = {};
		positionGrid = {};
		x = chunkPosX;
		z = chunkPosZ;
	}
	
	setmetatable(chunk,Chunk)
	
	local positionGrid = chunk.positionGrid
	
	for x = 0, X do
		positionGrid[x] = {}

		for z = 0, Z do
			positionGrid[x][z] = getPosition(chunkPosX,chunkPosZ,x,z)
		end
	end

	for x = 0, X-1 do
		for z = 0, Z-1 do
			local a = positionGrid[x][z]
			local b = positionGrid[x+1][z]
			local c = positionGrid[x][z+1]
			local d = positionGrid[x+1][z+1]

			local wedgeA, wedgeB = draw3DTriangle(a,b,c)
			local wedgeC, wedgeD = draw3DTriangle(b,c,d)
			
			paintWedge(wedgeA)
			paintWedge(wedgeB)
			paintWedge(wedgeC)
			paintWedge(wedgeD)
			
			table.insert(chunk.instances, wedgeA)
			table.insert(chunk.instances, wedgeB)
			table.insert(chunk.instances, wedgeC)
			table.insert(chunk.instances, wedgeD)
			--[[
			Terrain:FillBlock(wedgeA.CFrame,wedgeA.Size,Enum.Material.Sand)
			Terrain:FillBlock(wedgeB.CFrame,wedgeB.Size,Enum.Material.Sand)
			Terrain:FillBlock(wedgeC.CFrame,wedgeC.Size,Enum.Material.Sand)
			Terrain:FillBlock(wedgeD.CFrame,wedgeD.Size,Enum.Material.Sand)
			]]--
		end
	end
	
	addWater(chunk)
	addTrash(chunk)
	
	return chunk
end

function Chunk:Destroy()
	for index, instance in ipairs(self.instances) do
		instance:Destroy()
	end
	
	game.Workspace.Terrain:FillBlock(self.waterCFrame, self.waterSize, Enum.Material.Air)
end

return Chunk

The problem actually lies in the first few lines until defining the CFrame.

Ah, okay.

I’d place a breakpoint on the line immediately after if math.random() < TRASH_DENSITY then, to see if any trash actually gets generated. It may be that it does get generated but you can’t see it because it’s positioned incorrectly. If that’s no the case, you know that your “should I place trash or not” logic is the issue.

p will always be the same for EVERY call, because math.noise doesn’t return random numbers. It always returns the same output if you give it the same input, which is exactly what you’re doing (assuming TRASH_SEED is a constant).

r will always be a number, and all numbers are truthy, so the only branch that can ever be reached is the r = #garbage branch. It’s also confusing to have a “not something” as the first branch, and you don’t need an elseif because (not not r) guarantees (r). I’d change it to this:

local r = math.ceil(q+1)
if r then
	continue
else
	r = #garbage
end
1 Like

At the end of the if math.random() < TRASH_DENSITY then block, I increment the TRASH_SEED by one hundredth. Let’s say that…

TRASH_SEED = 69420.01	--	(nice)

then…

math.noise(TRASH_SEED)	--	0.0078171389177442
TRASH_SEED += 0.01		--	new TRASH_SEED value: 69420.02
math.noise(TRASH_SEED)	--	0.023555938154459

This is to counteract the constant “p will always be the same for EVERY call” problem.

As for the different r value, thanks for clearing up the conditional statement. At least it seems clearer and that I do not have to set continue before end.