# Help with map generation using Perlin noise (SOLVED)

#1

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

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 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),
lerp(u, grad(self.p[AB+1], x  , y-1, z-1),
end

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.

0 Likes

#2

Send the whole project in a zip.

1 Like

#3

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

#4

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

#5

Here is it @sergey.lerg
Noise.zip (37.3 KB)

0 Likes

#6

@Pkeod I would like to generate something like this:

I’m using the same code.

0 Likes

#7

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

``````
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

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 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),
lerp(u, grad(self.p[AB+1], x  , y-1, z-1),
end

return perlin
``````

And how to use it in your scene

``````local perlin = require('main.perlin_noise')

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}
end
``````
5 Likes

#8

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

1 Like

#9

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``````
0 Likes

#10

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')

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"))
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 }
end
``````

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

0 Likes

#11

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

``````local perlin = require('main.perlin_noise')

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}
end
``````
6 Likes

#12

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

#13

I still did small cosmetic changes:

``````local perlin = require('main.perlin_noise')

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}
end
``````

I added snow and better sand colors.

2 Likes

#14

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.

``````local perlin = require('main.perlin_noise')

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)
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)
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}
end``````
7 Likes

#15

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.

0 Likes

#16

What you most likely want is fractal Brownian motion.

4 Likes

#17

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
``````
3 Likes

#18

Thank you very much.

0 Likes

#19

@sergey.lerg It’s workin fantastics!

2 Likes