Help with map generation using Perlin noise (SOLVED)

Hi, I know there has been a similar question before, but I would love for someone to clarify my problem that I can’t understand. In short, this is my module, which I draw using DrawPixels function:

– original code by Ken Perlin: http://mrl.nyu.edu/~perlin/noise/
local function BitAND(a,b)–Bitwise and
local p,c=1,0
while a>0 and b>0 do
local ra,rb=a%2,b%2
if ra+rb>1 then c=c+p end
a,b,p=(a-ra)/2,(b-rb)/2,p*2
end
return c
end

	perlin = {}
	perlin.p = {}
	perlin.permutation = { 151,160,137,91,90,15,
	131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
	190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
	88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
	77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
	102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
	135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
	5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
	223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
	129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
	251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
	49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
	138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
	}
	perlin.size = 256
	perlin.gx = {}
	perlin.gy = {}
	perlin.randMax = 256

	function perlin:load(  )
	for i=1,self.size do
		self.p[i] = self.permutation[i]
		self.p[255+i] = self.p[i]
	end
	end

	function perlin:noise( x, y, z )
	local X = BitAND(math.floor(x), 255) + 1
	local Y = BitAND(math.floor(y), 255) + 1
	local Z = BitAND(math.floor(z), 255) + 1

	x = x - math.floor(x)
	y = y - math.floor(y)
	z = z - math.floor(z)
	local u = fade(x)
	local v = fade(y)
	local w = fade(z)
	local A  = self.p[X]+Y
	local AA = self.p[A]+Z
	local AB = self.p[A+1]+Z
	local B  = self.p[X+1]+Y
	local BA = self.p[B]+Z
	local BB = self.p[B+1]+Z
	return lerp(w, lerp(v, lerp(u, grad(self.p[AA  ], x  , y  , z  ),
	grad(self.p[BA  ], x-1, y  , z  )),
	lerp(u, grad(self.p[AB  ], x  , y-1, z  ),
	grad(self.p[BB  ], x-1, y-1, z  ))),
	lerp(v, lerp(u, grad(self.p[AA+1], x  , y  , z-1),
	grad(self.p[BA+1], x-1, y  , z-1)),
	lerp(u, grad(self.p[AB+1], x  , y-1, z-1),
	grad(self.p[BB+1], x-1, y-1, z-1))))
	end


	function fade( t )
	return t * t * t * (t * (t * 6 - 15) + 10)
	end

	function lerp( t, a, b )
	return a + t * (b - a)
	end

	function grad( hash, x, y, z )
	local h = BitAND(hash, 15)
	local u = h < 8 and x or y
	local v = h < 4 and y or ((h == 12 or h == 14) and x or z)
	return ((h and 1) == 0 and u or -u) + ((h and 2) == 0 and v or -v)
	end

And this is the result:

Strange, isn’t it? I’d like to draw islands, but I don’t know how to do it. I will be grateful for any idea or comment.

1 Like

Send the whole project in a zip.

1 Like

The result looks normal to me. Perlin Noise generates a noise grid with values typically between -1/sqrt(2) and 1/sqrt(2). You should be able to scan the resulting values and assign semantics to a set number of ranges, such as 0.1 - 0.3 = tile_id.ground. If there is too much variability, try altering the noise parameters like altitude, frequency, etc (or implementing the parameters into the module if they don’t exist already.)

Another option could be to look into incorporating “Brownian Motion” along with the Perlin Noise. It helps to produce some very nice results.

1 Like

Can you share a visualization / example of what you want? There are various ways to get islands with perlin noise. Usually you’ll want to layer multiple noise data on top of each other. It can take a lot of experimentation to get the look you want. Look up octaves / perlin noise online for lots of videos and articles which may help.

1 Like

Here is it @sergey.lerg
Noise.zip (37.3 KB)
Thank you so much for your answers.

1 Like

@Pkeod I would like to generate something like this:

From this article:


I’m using the same code.

I’ve fixed the Perlin noise library and your code, there were many issues.

Now it looks correctly how a Perlin noise should look like. Based on that you can generate islands.

Here is the fixed project.
Noise.zip (10.6 KB)

Here is the code for the Perlin noise library


local function fade(t)
	return t * t * t * (t * (t * 6 - 15) + 10)
end

local function lerp(t, a, b)
	return a + t * (b - a)
end

local function grad(hash, x, y, z)
	local h = hash % 16
	local u, v, r

	if h < 8 then u = x else u = y end
	if h < 4 then v = y elseif h == 12 or h == 14 then v = x else v = z end
	if h % 2 == 0 then r = u else r = -u end
	if h % 4 == 0 then r = r + v else r = r - v end
	return r
end

local perlin = {}
perlin.p = {}
perlin.permutation = { 151,160,137,91,90,15,
	131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
	190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
	88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
	77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
	102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
	135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
	5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
	223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
	129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
	251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
	49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
	138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
}
perlin.size = 256

function perlin:load()
	for i = 0, self.size - 1 do
		self.p[i] = self.permutation[i + 1]
		self.p[self.size + i] = self.p[i]
	end
end

function perlin:noise(x, y, z)
	local X = math.floor(x % 255)
	local Y = math.floor(y % 255)
	local Z = math.floor(z % 255)

	x = x - math.floor(x)
	y = y - math.floor(y)
	z = z - math.floor(z)
	local u = fade(x)
	local v = fade(y)
	local w = fade(z)
	local A  = self.p[X] + Y
	local AA = self.p[A] + Z
	local AB = self.p[A + 1] + Z
	local B  = self.p[X + 1] + Y
	local BA = self.p[B] + Z
	local BB = self.p[B + 1] + Z
	return lerp(w, 	lerp(v, lerp(u,	grad(self.p[AA  ], x  , y  , z  ),
									grad(self.p[BA  ], x-1, y  , z  )),
							lerp(u, grad(self.p[AB  ], x  , y-1, z  ),
									grad(self.p[BB  ], x-1, y-1, z  ))),
					lerp(v, lerp(u, grad(self.p[AA+1], x  , y  , z-1),
									grad(self.p[BA+1], x-1, y  , z-1)),
							lerp(u, grad(self.p[AB+1], x  , y-1, z-1),
									grad(self.p[BB+1], x-1, y-1, z-1))))
end

return perlin

And how to use it in your scene

local perlin = require('main.perlin_noise')
perlin:load()

local luminance = hash('luminance')

function init(self)
	local width = 2048
	local height = 1024
	
	self.buffer = buffer.create(width * height, {{name = luminance, type=buffer.VALUE_TYPE_UINT8, count = 1}})
	local stream = buffer.get_stream(self.buffer, luminance)
	
	local scale = 1 / 50
	for x = 0, width - 1 do
		for y = 0, height - 1 do
			local v = (0.5 + perlin:noise(x * scale, y * scale, 0) / 2) * 255
			stream[x + y * width + 1] = v
		end
	end

	local resource_path = go.get('#sprite', 'texture0')
	local header = {width = width, height = height, type = resource.TEXTURE_TYPE_2D, format = resource.TEXTURE_FORMAT_LUMINANCE, num_mip_maps = 1}
	resource.set_texture(resource_path, header, self.buffer)
end
6 Likes

Thank you very much, I am really happy. Your code works great!

1 Like

I’m so sorry, but I have one more problem. I can’t draw noise in different colors, it’s always just reddish. Not sure how to set stream correctly for color change?

 local v = (0.5 + perlin:noise(x * scale, y * scale, 0) / 2) * 255
 stream[x + y * width + 1] = v

I already have it, I just replaced the “lumince” with RGB. And I made a few more minor adjustments. Here is the code:

local perlin = require('main.perlin_noise')
perlin:load()

function init(self)
	width = 2048
	height = 1024
	-- create RGBA buffer and get the stream so we can manipulate it using Lua
	self.buffer = buffer.create(width * height, { {name=hash("rgba"), type=buffer.VALUE_TYPE_UINT8, count=4} } )
	stream = buffer.get_stream(self.buffer, hash("rgba"))
	perlin:load()
	local scale = 1 / 50

	for x = 1, width - 1 do
		for y = 1, height - 1 do
			local v = math.floor((0.5 + perlin:noise(x * scale, y * scale, 0) / 2) * 255)
			local index = y * 4 * width + x * 4 + 1
			stream[index + 0] = v
		end
	end

	local resource_path = go.get("#sprite", "texture0")
	local header = { width = width, height = height, type = resource.TEXTURE_TYPE_2D, format = resource.TEXTURE_FORMAT_RGBA, num_mip_maps = 1 }
	resource.set_texture( resource_path, header, self.buffer )
end

Thank you all again, especially to you @sergey.lerg.

Here is how you can create a color image with islands.

local perlin = require('main.perlin_noise')
perlin:load()

local rgb = hash('rgb')

local colors = {
	water = 255 * vmath.vector3(0.2, 0.5, 1),
	sand = 255 * vmath.vector3(0.8, 0.9, 0.6),
	grass = 255 * vmath.vector3(0.2, 0.8, 0.4),
	rock = 255 * vmath.vector3(0.7, 0.7, 0.7)
}

local function set_pixel(stream, width, x, y, color)
	local index = 3 * (x + y * width) + 1
	stream[index] = color.x
	stream[index + 1] = color.y
	stream[index + 2] = color.z
end

function init(self)
	local width = 2048
	local height = 1024
	
	self.buffer = buffer.create(width * height, {{name = rgb, type = buffer.VALUE_TYPE_UINT8, count = 3}})
	local stream = buffer.get_stream(self.buffer, rgb)
	
	local scale = 1 / 50
	for x = 0, width - 1 do
		for y = 0, height - 1 do
			local v = 0.5 + perlin:noise(x * scale, y * scale, 0) / 2
			local color = colors.rock
			if v < 0.55 then
				color = colors.water
			elseif v < 0.7 then
				color = colors.sand
			elseif v < 0.8 then
				color = colors.grass
			end
			set_pixel(stream, width, x, y, color)
		end
	end

	local resource_path = go.get('#sprite', 'texture0')
	local header = {width = width, height = height, type = resource.TEXTURE_TYPE_2D, format = resource.TEXTURE_FORMAT_RGB, num_mip_maps = 1}
	resource.set_texture(resource_path, header, self.buffer)
end
6 Likes

This is really great, I’ve already done something similar, but your code is much more fine-tuned. Thank you very much @sergey.lerg

1 Like

I still did small cosmetic changes:

local perlin = require('main.perlin_noise')
perlin:load()

local rgb = hash('rgb')

local colors = {
	water = 255 * vmath.vector3(0.2, 0.5, 1),
	sand = 255 * vmath.vector3(1, 0.9, 0.6),
	grass = 255 * vmath.vector3(0.2, 0.8, 0.4),
	rock = 255 * vmath.vector3(0.7, 0.7, 0.7),
	snow = 255 * vmath.vector3(1, 1, 1)
}

local function set_pixel(stream, width, x, y, color)
	local index = 3 * (x + y * width) + 1
	stream[index] = color.x
	stream[index + 1] = color.y
	stream[index + 2] = color.z
end

function init(self)
	local width = 2048
	local height = 1024

	self.buffer = buffer.create(width * height, {{name = rgb, type = buffer.VALUE_TYPE_UINT8, count = 3}})
	local stream = buffer.get_stream(self.buffer, rgb)

	local scale = 1 / 50
	for x = 0, width - 1 do
		for y = 0, height - 1 do
			local v = 0.5 + perlin:noise(x * scale, y * scale, 0) / 2
			local color = colors.rock
			if v < 0.55 then
				color = colors.water
			elseif v < 0.6 then
				color = colors.sand
			elseif v < 0.7 then
				color = colors.grass
			elseif v > 0.78 then
				color = colors.snow
			end
			set_pixel(stream, width, x, y, color)
		end
	end

	local resource_path = go.get('#sprite', 'texture0')
	local header = {width = width, height = height, type = resource.TEXTURE_TYPE_2D, format = resource.TEXTURE_FORMAT_RGB, num_mip_maps = 1}
	resource.set_texture(resource_path, header, self.buffer)
end

I added snow and better sand colors.

2 Likes

Again, I made minor code modifications and achieved something. I’m trying to generate islands and I’d say I already have a good foundation for the program.

image

local perlin = require('main.perlin_noise')
perlin:load()

local rgb = hash('rgb')

local colors = {
	water = 255 * vmath.vector3(0.2, 0.5, 1),
	sand = 255 * vmath.vector3(1, 0.9, 0.6),
	grass = 255 * vmath.vector3(0.2, 0.8, 0.4),
	rock = 255 * vmath.vector3(0.7, 0.7, 0.7),
	snow = 255 * vmath.vector3(1, 1, 1)
}

local function set_pixel(stream, width, x, y, color)
	local index = 3 * (x + y * width) + 1
	stream[index] = color.x
	stream[index + 1] = color.y
	stream[index + 2] = color.z
end

function init(self)
	local width = 2048
	local height = 1024

	self.buffer = buffer.create(width * height, {{name = rgb, type = buffer.VALUE_TYPE_UINT8, count = 3}})
	local stream = buffer.get_stream(self.buffer, rgb)

	local scale = 1 / 40
	pos = vmath.vector3(1024, 512, 0)
	radius = 200
	for x = 0, width - 1 do
		for y = 0, height - 1 do
			local v = 0.5 + perlin:noise(x * scale, y * scale, 0) / 2
			distance = vmath.length(vmath.vector3(x, y, 0) - pos)
			if distance < radius then
				part = distance / (radius/100) 
				v = v - ((v/100) * part)
			else v = 0 end
			
			local color = colors.rock
			if v < 0.1 then
				color = colors.water
			elseif v < 0.13 then
				color = colors.sand
			elseif v < 0.35 then
				color = colors.grass
			elseif v > 0.5 then
				color = colors.snow
			end
			set_pixel(stream, width, x, y, color)
		end
	end

	local resource_path = go.get('#sprite', 'texture0')
	local header = {width = width, height = height, type = resource.TEXTURE_TYPE_2D, format = resource.TEXTURE_FORMAT_RGB, num_mip_maps = 1}
	resource.set_texture(resource_path, header, self.buffer)
end
7 Likes

Hi, I have one more question. Would anyone know how to use this code to make the result look more like this?


and not like this:

Thank you very much for every answer.

What you most likely want is fractal Brownian motion.

4 Likes

You need to combine several noise values at different frequencies.

local elevation = 0.5 + (perlin:noise(x * scale, y * scale, 0) + 0.5 * perlin:noise(2 * x * scale, 2 * y * scale, 0.1) + 0.25 * perlin:noise(4 * x * scale, 4 * y * scale, 0.2)) / 2
4 Likes

Thank you very much.

@sergey.lerg It’s workin fantastics!

3 Likes