How to generate planets with mountains

im trying to make a script that can generate mountains through noise, but when i put in noise it looks nothing like mountains and hills

local steps = 0
local layer = 0
local position

local origin = workspace.PlanetCenter.Position
local diameter = 500
local segmentSize = Vector3.new(30, 30, 10)

local seed = math.random(1, 10e6)

function getMaterial(height)
	if height <= 20 then
		return Enum.Material.Grass
	elseif height <= 50 then
		return Enum.Material.Rock
	elseif height <= 100 then
		return Enum.Material.Snow
	elseif height > 100 then
		return Enum.Material.Snow
	end
end

local noiseScale = 200
local amplitude = 200

repeat
	repeat
		local height = math.noise(steps / noiseScale, layer / noiseScale, seed) * amplitude
		position = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(diameter / 2))
		workspace.Terrain:FillBlock(((position + position.LookVector * -height) + origin), segmentSize, getMaterial(height))
		steps = steps + 1
	until steps == 360
	steps = 0
	layer = layer + 1
	task.wait()
until layer == 270
print('done')

anyone know how to make it look better?

3 Likes

Looks like it’s got lumps. Try a higher frequency to make the lumps smaller, that should give a better “rolling hills” effect. For mountain ranges you’ll want sharp “cusps”. A simple way to get that is to take the negated absolute value of a height function.

E.g with sin because GeoGebra doesn’t have built in coherent noise:

image

1 Like

so im assuming that noise scale is the frequency and increasing that did nothing


increased it to 5000

but lowering it causes this


5 noiseScale

which isnt what i really want
its too bumpy and isnt really a hill more like big pillars

now this is a scale of 20

this is closer but now there is so many hills. is there any way to make them more spread out?

sometimes this happens which is strange

and finally when meeting the end of the generation all of it will converge onto this one point
image

1 Like

No, “scale” is period, which is the inverse of frequency. So higher frequency means lower period and vice versa. The way to fix it for sine waves is to pick an integer frequency, because the angle goes from 0 to 2pi and sin repeats after 2pi.

Yes, by decreasing the frequency <=> increasing the period.

I think that might happen if you increase the frequency without decreasing the amplitude. So try decreasing the amplitude, I think that might give you the small hills your want.

It looks like a discontinuity in the height map. Here’s an illustration using sine waves again:

The wavy figure is the height map but wrapped around a circle. The red line graphs a function representing the angle, and the blue one a function representing the radius (height). Because of the specific frequency/period I choice, there’s a gap around theta=0, 2pi that could explain what you’re seeing. It can be fixed for sine waves, but not for the implementation of Perlin noise that Roblox uses since it doesn’t repeat :frowning: You can pick frequencies that at least give the same value at the seam, but not the same gradients unfortunately:

image

You can still see the discontinuity near the bottom of that screenshot.

A solution is to just work in 3D instead. Compute the 3D position on a sphere and use all 3 dimensions of math.noise. Here’s what that looks like in 2D:

image

Made with this code:

local height = 40
local period = 1/10
local amplitude = 10

for _a = 0, 1, 1/300 do
	local a = math.pi*2*_a
	local r = height
	local x, z = r * math.cos(a), r * math.sin(a)
	local v = math.noise(x * period, z * period) * amplitude
	r = r + v
	x, z = r * math.cos(a), r * math.sin(a)
	local p = script.Part:Clone()
	p.CFrame = CFrame.new(x, 0, z)
	p.Parent = game.Workspace
end

Notice that it’s generating the x and z coordinates twice. First to find the point on the sphere, so that point can be used as input to math.noise, and then again to find the point on the sphere moved out of the sphere by the noise function.

I think that might get fixed by the 3D approach I mentioned just before.

3 Likes

so how would i modify this code so it wraps around the sphere i generate?

1 Like

Your existing way of generating points on the sphere is fine. Use the X, Y and Z of each point as input to math.noise. Generate the points again, but adding the output of math.noise to the radius.

1 Like

so something like this?

-- position is a cframe
local nosie = math.noise(position.Position.X * period, position.Position.Z * period, seed) * amplitude 

this will return the Y? or am i doing something wrong
im new to noise and dont really know much about it

or do i do this

local nosie = math.noise(position.Position.X * period, position.Position.Y * period, position.Position.Z * period) * amplitude 
radius += noise

but doing this removes the seed and i dont want every planet to be generated with the same noise. i still need the seed in it
nvm the position of each planet is different

so this is what i think the full code will look like

local steps = 0
local layer = 0
local position

local origin = workspace.PlanetCenter.Position
local diameter = 500
local segmentSize = Vector3.new(30, 30, 10)

local seed = math.random(1, 10e6)

function getMaterial(height)
	if height <= 20 then
		return Enum.Material.Grass
	elseif height <= 50 then
		return Enum.Material.Rock
	elseif height <= 100 then
		return Enum.Material.Snow
	elseif height > 100 then
		return Enum.Material.Snow
	end
end

local period = 1/10
local amplitude = 200
local radius = diameter / 2

repeat
	repeat
		position = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(radius))
                local nosie = math.noise(position.Position.X * period, position.Position.Y * period, position.Position.Z * period) * amplitude 
                local newRadius = radius + noise
                
                local newPos = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(newRadius))
		workspace.Terrain:FillBlock(newPos + origin, segmentSize, Enum.Material.Grass)
		steps = steps + 1
	until steps == 360
	steps = 0
	layer = layer + 1
	task.wait()
until layer == 270
print('done')

result

i will continue messing with it to get the result i want and ill edit this message when finished

3 Likes

update: this is the best sphere i got
i may of did the noise wrong

code:

local steps = 0
local layer = 0

local origin = workspace.PlanetCenter.Position
local diameter = 500
local segmentSize = Vector3.new(30, 30, 10)

local seed = math.random(1, 10e6)

function getMaterial(height)
	if height <= 50 then
		return Enum.Material.Sandstone
	elseif height < 100 then
		return Enum.Material.Rock
	end
	
	return Enum.Material.Snow
end

local period = 1/50 --[[ smaller = more --]]
local amplitude = 30
local radius = diameter / 2

repeat
	repeat
		local position = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(radius))
		local nosie = math.noise(position.Position.X * period, position.Position.Y * period, position.Position.Z * period) * amplitude 
		local newRadius = radius + nosie

		local newPos = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(newRadius))
		workspace.Terrain:FillBlock(newPos + origin, segmentSize, Enum.Material.Sandstone)
		steps = steps + 1
	until steps == 360
	steps = 0
	layer = layer + 1
	task.wait()
until layer == 90
print('done 1')

steps = 0
layer = 0
repeat
	repeat
		local position = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(radius))
		local nosie = math.noise(position.Position.X * period, position.Position.Y * period, position.Position.Z * period) * amplitude 
		local newRadius = radius + nosie

		local newPos = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(newRadius))
		workspace.Terrain:FillBlock(newPos + origin, segmentSize, Enum.Material.Sandstone)
		steps = steps + 1
	until steps == 360
	steps = 0
	layer = layer - 1
	task.wait()
until layer == -90
print('done 2')

its still not really what i want. i want hills that are spread out, these are too close together

1 Like

ok this is the best i got

local steps = 0
local layer = 0

local origin = workspace.PlanetCenter.Position
local diameter = 500
local segmentSize = Vector3.new(30, 30, 10)

local seed = math.random(0,10e6)

local noiseScale = 1/200
local amplitude = 100

function getMaterial(height)
	if height <= 50 then
		return Enum.Material.Sandstone
	elseif height < 100 then
		return Enum.Material.Rock
	end
	
	return Enum.Material.Snow
end

local radius = diameter / 2

repeat
	repeat
		local position = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(radius))
		local cx, cy, cz = position.Position.x, position.Position.y, position.Position.z
		
		local noise = math.noise(cx * noiseScale, cy * noiseScale, cz * noiseScale, seed) * amplitude
		local newRadius = radius + noise
		local newPos = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(newRadius))
		workspace.Terrain:FillBlock(newPos + origin, segmentSize, Enum.Material.Sandstone)
		steps = steps + 1
	until steps == 360
	steps = 0
	layer = layer + 1
	task.wait()
until layer == 90
print('done 1')

steps = 0
layer = 0
repeat
	repeat
		local position = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(radius))
		local cx, cy, cz = position.Position.x, position.Position.y, position.Position.z

		local noise = math.noise(cx * noiseScale, cy * noiseScale, cz * noiseScale, seed) * amplitude
		local newRadius = radius + noise
		local newPos = CFrame.Angles(math.rad(steps), math.rad(-layer), math.rad(0)) * CFrame.new(0, 0, math.abs(newRadius))
		workspace.Terrain:FillBlock(newPos + origin, segmentSize, Enum.Material.Sandstone)
		steps = steps + 1
	until steps == 360
	steps = 0
	layer = layer - 1
	task.wait()
until layer == -90
print('done 2')

its still doesnt look right

1 Like

but that is because is not very big the planet