[OPEN SOURCED] Custom Terrain Height Map

I made a prototype of a custom height map generator. This is my first attempt, and I’m open to suggestions and optimizations. It’s showcased here in this video:
Enjoy the satisfying blocks.

NEW VERSION THAT IS OPEN SOURCED:

The generation speed was significantly decreased for the purpose of this video. I’m not sure whether I’ll actually do anything with it, but I’m still pretty happy with how it came out.

Here’s the source:

46 Likes

You should share source code, then!

What’s the general algorithm? Random assignment of pixels followed by some sort of blur (like, averaging all pixels within 4 blocks of a point)?

It’s cool that it works in chunks which is always nice if you want to have the map expand gradually.

That’s exactly how it works. I actually improved it recently with settings that can add more random splotches of color. The code itself could be cleaned up a tad, but I could open source it I guess. I had the idea to turn it into an island generator thing for some kind of ambitious game I know I’ll never finish.

1 Like

I just made it SO much better by smearing the colors randomly instead of in an order. It does all parts but in a random order!

And it’s done! Packed with settings for each and every detail.

2 Likes

I’d imagine that a chunkloader would significantly improve this, if you are going to use it.
EDIT: Moved topic to Community Resources because it’s now a tool.

Update: Found old code that caused the script to error; fixed.

Here’s the new version compared to the original video:

1 Like

This is a plug-in that I’ve been looking for for ages, but I wonder if someone could create a plug-in that creates smooth terrain with correct collisions that wouldn’t lag as much as normal unions or parts. If that’s really complex, could someone try and make a plug-in that converts meshes into smaller meshes or unions somehow? Maybe the game could scan the mesh’s polygons and somehow recreate them into unions or meshes. Both would be hard but I think it’s do-able.

Im not really a scripter so please do correct me if there are flaws :slightly_smiling_face:

It is possible to convert the parts to terrain instead. Otherwise, there are calculations that can be done to fill in the gaps and transform it into something low poly looking. This is very difficult to my knowledge, but using the real-time csg, it’s possible.

How do you do it? I’ve been looking for one for ages.

If you mean roblox terrain, then just use terrain functions to fill it in instead of parts. There’s a whole API for that. If you mean low poly terrain, you’d have to create two triangles for every part and connect them to the nearest corners. It’s no easy task, but the low poly terrain generator plugin probably has some useful code.

I’ve been making an altered version that creates islands. Here’s my progress:

Also proof of Lighting.Ambient at work:

Interesting to see how it handles island overlapping.

thinkin’ about a minecraft game over here

2 Likes

I improved it a bit because I was bored, feel free to use this:

ModuleScript:

local module = {}
function module.generateMap(config)
	config = config or {}
	config.heightMultiplier = config.heightMultiplier or 150 --all settings are optional
	config.variety = config.variety or 100
	config.tileSize = config.tileSize or 3
	config.blocksUnderMainLayer = config.blocksUnderMainLayer or 5
	config.origin = config.origin or Vector3.new(0, 0, 0)
	config.mapSize = config.mapSize or 64
	config.waitTime = config.waitTime or 100
	config.makeTilesSameColor = config.makeTilesSameColor or false
	config.tileColor = config.tileColor or Color3.fromRGB(163, 162, 165)
	
	local model = Instance.new("Model")
	if workspace:FindFirstChild("Map1") then --unique model name
		local num = 1
		repeat
			num += 1
			model.Name = "Map"..num
		until not workspace:FindFirstChild(model.Name)
	else
		model.Name = "Map1"
	end
	model.Parent = workspace
	local template = Instance.new("Part")
	template.Anchored = true
	template.Size = Vector3.new(config.tileSize, config.tileSize, config.tileSize)
	template.TopSurface = Enum.SurfaceType.Smooth
	template.BottomSurface = Enum.SurfaceType.Smooth
	
	for x = 1, config.mapSize do
		for z = 0, config.mapSize - 1 do
			local part = template:Clone()
			part.CFrame = CFrame.new(config.origin + Vector3.new(x*config.tileSize, 0, z*config.tileSize))
			part.Parent = model
		end
	end
	--create noise
	for w, v in pairs(model:GetChildren()) do
		local brightness = (math.random(0,100)/100)*255
		if math.random(1,100) == 50 then
			brightness = (math.random(0,100)/100)*255
			local mult = math.random(0,200)/40
			local region = Region3.new(v.Position - Vector3.new(v.Size.X*mult, 100, v.Size.Z*mult), v.Position + Vector3.new(v.Size.X*mult, 100, v.Size.Z*mult))
			for _, v2 in pairs(game.Workspace:FindPartsInRegion3(region, v)) do
				v2.Color = Color3.fromRGB(((v.Color.r+v2.Color.r)/2)*255, ((v.Color.g+v2.Color.g)/2)*255, ((v.Color.g+v2.Color.g)/2)*255)
			end
		end
		v.Color = Color3.fromRGB(brightness, brightness, brightness)
		if math.fmod(w, config.waitTime*6) == 0 then
			wait()
		end
	end
	--smear color values in a random order
	local tiles = model:GetChildren()
	for w = 1,#tiles do
		local pos = math.random(1,#tiles)
		local v = tiles[pos]
		local mult = math.random(0,200)/config.variety
		local region = Region3.new(v.Position - Vector3.new(v.Size.X*mult, 100, v.Size.Z*mult), v.Position + Vector3.new(v.Size.X*mult, 100, v.Size.Z*mult))
		for _,v2 in pairs(game.Workspace:FindPartsInRegion3(region, v)) do
			v2.Color = Color3.fromRGB(((v.Color.r+v2.Color.r)/2)*255, ((v.Color.g+v2.Color.g)/2)*255, ((v.Color.g+v2.Color.g)/2)*255)
		end
		table.remove(tiles, pos)
		if math.fmod(w, config.waitTime) == 0 then
			wait()
		end
	end
	--height map based on hsv value
	for w, v in pairs(model:GetChildren()) do
		local height = math.floor(math.floor(v.Color.r*(config.heightMultiplier))/(config.tileSize*2))*config.tileSize
		v.CFrame = CFrame.new(v.Position.X, height, v.Position.Z)
		if math.fmod(w, config.waitTime) == 0 then
			wait()
		end
	end
	--place blocks under the main layer
	for w, v in pairs(model:GetChildren()) do
		local tilesToGround = v.Position.Y/config.tileSize
		local maxdepth = config.heightMultiplier / 25
		for i = 1, config.blocksUnderMainLayer do
			if tilesToGround - i > maxdepth then
				local part = template:Clone()
				part.CFrame = CFrame.new(v.Position.X, v.Position.Y - (config.tileSize*i), v.Position.Z)
				part.Parent = model
			end
		end
		if math.fmod(w, config.waitTime*2) == 0 then
			wait()
		end
	end
	
	if config.makeTilesSameColor then
		for w, v in pairs(model:GetChildren()) do
			v.Color = config.tileColor
			if math.fmod(w, config.waitTime*2) == 0 then
				wait()
			end
		end
	end
	print("Generated map")
	return model
end
return module

Script:

local module = require(game.ServerStorage.TerrainGenerator)
local config = {
----GENERATION-SETTINGS----------------------------

	heightMultiplier = 150, --This controls height intensity
	variety = 100, --Lower this to increase smear
	tileSize = 3,
	blocksUnderMainLayer = 5, --Depends on heightMultiplier
	
----MAP-SETTINGS-----------------------------------

	origin = Vector3.new(0, 0, 0),
	mapSize = 100, --How many TILES, not studs
	waitTime = 100, --Controls how long it waits between each modification. Lowering this causes more wait time
	makeTilesSameColor = false,
	tileColor = Color3.fromRGB(163, 162, 165), --Only relevant when makeTilesSameColor = true
}

--All settings are optional! You can also just use module.generateMap()
--The function returns the map


--Examples:
local map = module.generateMap(config)
module.generateMap({origin = Vector3.new(300, 0, 0)})

--[[
Mountains:
local config = {
	heightMultiplier = 350,
	blocksUnderMainLayer = 10,
	origin = Vector3.new(0, 0, 0),
	mapSize = 50,	
}

Flat area:
local config = {
	heightMultiplier = 40,
	variety = 60,
	origin = Vector3.new(0, 0, 0),
	mapSize = 50,
}
]]