Noise ?

Hi All,

I have dabbled with noise - Perlin, simplex etc and used a few methods to design heightmap. With the arrival of Craft the noise functions have been significantly improved with lots of options to play with. Another star for Craft. But, unfortunately I’m not a mathematician and my early attempts are very crude. What I’m looking for is how to use coherent noise - example calls and the noisegradients you can generate.

Anyone got any ideas ?

@Bri_G The different noise functions just return different values for the same input values. The example below shows the same input x,y,z values for 3 different noise functions. The output results from them are different. To see a better representation, a 3 dimensional grid should be used.

function setup()
    x,y,z=.1,.2,.3
    
    n = craft.noise.perlin()
    value = n:getValue(x,y,z)
    print(value)
    
    n = craft.noise.rigidMulti()
    value = n:getValue(x,y,z)
    print(value)
    
    n = craft.noise.billow()
    value = n:getValue(x,y,z)
    print(value)
end

@Bri_G I thru together this 3D graph of some noise functions. When you run this, it takes a few seconds before the graph displays. To see the different noise results, uncomment each noise function in the for loops. The grid calculations are from x -1 To 1, y -1 To 1, and z -1 to 1 in steps of .2 . The y value is replaced with the calculation from the noise function to show the different noise values.

I deleted the code because of some mistakes I made.
An updated version is shown below.

@dave1707 - pretty awesome and prime candidate for a Craft example. Could improve slightly if you can switch off the ground or make it transparent then viewed from above you can see the 2 dimensional spread of the data ie a rectangle.

@Bri_G I have other changes I’m going to make to the code, so I can add a switch that shows/hides the ground.

@Bri_G I added your suggestion in the code below. There’s a slider to show/hide the ground. I also added a slider to allow the noise function to use a value other than 0 for the y axis. That way you can see the differences with different starting y values.

supportedOrientations(LANDSCAPE_ANY)

function setup()
    assert(craft, "Please include Craft as a dependency")
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")
    name={"Gradient","Perlin","Billow","Const","RigidMulti"}
    nTab={craft.noise.gradient(),craft.noise.perlin(),
        craft.noise.billow(),craft.noise.const(),craft.noise.rigidMulti()}
    tab={}

    fill(255, 0, 0, 255)
    scene = craft.scene()
    skyMaterial=scene.sky.material
    skyMaterial.sky=color(0, 62, 255, 255)
    skyMaterial.horizon=color(99, 255, 0, 255)
    scene.sun.rotation=quat.eulerAngles(20,45,-30)
    v=scene.camera:add(OrbitViewer, vec3(0,0,0), 30, 0, 200)
    v.rx=10
    v.ry=20
    parameter.integer("noise",1,#nTab,1)
    parameter.number("yVal",-1,2,0)
    parameter.action("calc",calc)
    parameter.boolean("hide ground",createGround)
    calc()
end

function createSphere(x,y,z)
    size=.12
    sphere1=scene:entity()
    s1=sphere1:add(craft.rigidbody,STATIC)
    sphere1.position=vec3(x,y,z)
    sphere1:add(craft.shape.sphere,size)
    sphere1.model = craft.model.icosphere(size,1)
    sphere1.material = craft.material("Materials:Specular")
    sphere1.material.diffuse=color(255,0,0)
    table.insert(tab,sphere1)
end

function draw()
    update(DeltaTime)
    scene:draw()	
    text("Slide your finger to rotate image",WIDTH/2,HEIGHT-25)
    text("Values from x =-1.4 to 1.4  and  z =-1.4 to 1.4    step size = .05",WIDTH/2,HEIGHT-50)
    text("y = "..yVal,WIDTH/2,HEIGHT-75)
    text("Noise function   =  "..name[noise],WIDTH/2,HEIGHT-100)
end

function update(dt)
    scene:update(dt)
end

function createGround()
    if hide_ground and ground then
        ground:destroy()
        ground=nil
    elseif not ground then
        ground = scene:entity()
        ground.model = craft.model.cube(vec3(15,.05,15))
        ground.material = craft.material("Materials:Specular")
        ground.material.map = readImage("Surfaces:Desert Cliff Roughness") 
    end
end

function calc()
    output.clear()
    print("Calculating... please wait.")
    for z=1,#tab do
        tab[z]:destroy()
    end
    collectgarbage()
    tab={}
    step=.05
    n=nTab[noise]
    for x=-1.4,1.4, step do
        for z=-1.4,1.4,step do
            value = n:getValue(x,yVal,z)
            createSphere(x*5,value*5,z*5)
        end
    end
    output.clear()
    print("Change noise for another noise function then press calc.")
end

@dave1707 - thanks for that. Tried those out but ran into a problem when I tried to use craft.noise.constant() which threw up an error even with a value in the parentheses a la reference. Tried n:getValue(x,y,z) as against n(x,y,z) and the output noise.constant printed.

Being naive I thought noise was just another random function and expected to get random output. But that’s not the case - you get the same output every time from the same input. They are just mathematical functions that give different output from different input, but reproducible. Just need to bend them to our needs.

Trying to apply the features of lib.noise a la their website

Libnoise

What I’m trying to get my head round is coherent-noise.

@Bri_G From what I know about coherent noise is:

  1. It returns the same output for the same input.
  2. A small input change results in a small output change
  3. A large input change results in a different output change.

It’s like playing a CD. If you play the same CD, you get the same songs. If you turn up the volume a little, all the songs are the same but a little louder. If you turn up the volume a lot, it’s the same songs but distorted. Might not be a good example.

Here’s an updated 3D graphic of some Craft noise functions. Change the noise slider, then press calc to show the noise results.

Removed the code.
See the updated version below.

@dave1707 - that’s neat, gives the user the chance to modify the data and see it from all angles. Nice coding.

@John

You may be able to answer this - regarding libnoise. In the Tutorial series, tutorial 3 shows the generation of a greyscale tile. It also shows a graph of the ‘bounding rectangle’ used for the generation. I can generate grey scale tiles without problem but and, as in a later tutorial, can produce tiles which extend the first tile matched at the interface.

However the tutorial series suggested that the tiles will wrap so I was hoping the far right face on the second tile would wrap to match the first tile left face and bottom wraps with top faces. But that is not the case.

Could you explain what the dimensions used in the graphs are - they seem out of proportion to the scale of the images (ie 2 to 6 versus 1 to 256).

Also I am assuming I will have to write some blending routines too get the desired face overlaps - is that the case. You have already written some in the Planet generator example for Craft so I will base my routines on yours.

Could you also advise on the x,y,z parameter ranges for the calls to the noiselib. The tutorial looks like the x and z parameters are used - if so what is the y parameter. I’ve just fiddled with them to generate my maps and they seem to need low (<0.01) numbers.

Finally, some of the images I have generated seem to have colours in strange places. I have only used positive numbers in my coding - it looks like the craft.noise.perlin(x,y,z) call returns numbers between -1 and +1, is that the case. If so my code will need some adjustment.

By the way figured out the skybox now I can use it with obj models - looks awesome. Will post a vid later.

@Bri_G I went to the libnoise page and thought I’d write this. Here’s the prelim noise from -1 To 1 in steps of .0075. I’m changing the color based on a range of the results from the perlin noise function. Slide your finger up or down to change the z value into the perlin noise. When you lift your finger, it takes a few seconds to recalculate a new image.

Code removed.
An updated version is below.

@John The lacunarity works for noise.billow() and noise.rigidMulti() but not for noise.perlin() even though it’s listed for noise.perlin(). I don’t know if the documentation is wrong or perlin is wrong.

@dave1707- thanks for the update, interesting way of generating map what are the -1 to +1 scales? Is it the ouput range from the perlin noise generator.bswitched it to greyscale and saw some straight line features. Intrigued by the z_level and its range is that a feature of perlin noise?

Thanks again slowly making progress here. Do you know if you can enhance the tiles like the tutorials for lib noise by brightening and changing the contrast - thought there may be features within Craft to do it or some shaders.

@dave1707 - thanks for the code my pad battery flat and watching Peaky Blinders so will check it out tomorrow. Found some of other code in John’s Voxel Terrain example. That will take a long time to digest but it answered my some of my needs with the frequency setting for the noise. Thanks again - respond again after a little rest and play.

@Bri_G If you’re wondering about frequency, I modified my program to allow you to change the settings for the perlin noise function. Just make a slider change, tap the screen to re-calc. @John The lacunarity value doesn’t work, is it the correct spelling in the documentation.

EDIT: Changed noise.perlin() to noise.billow(). Lacunarity works for the noise function billow but not for perlin. The parameter slider for lacunarity was uncommented.

supportedOrientations(LANDSCAPE_ANY)

function setup()
    parameter.integer("z_level",-50,50,0)
    parameter.integer("octaves",1,12,6)
    parameter.number("frequency",0,20,1)
    parameter.number("persistence",0,2,.5)
    parameter.number("lacunarity",0,10,2)
    parameter.integer("seed",0,10,0)
    rectMode(CENTER)
    backingMode(RETAINED)
    ee=true
    calc()
end

function calc()
    output.clear()
    print("Calculating")
    ee=true
    tab={}
    local v=craft.noise.billow() 
    v.octaves=octaves
    v.frequency=frequency
    v.persistence=persistence
    v.lacunarity=lacunarity
    v.seed=seed
    local step=.0075
    for x=-1,1,step do
        for y=-1,1,step do
            local z=v:getValue(x,y,z_level)    
            table.insert(tab,vec3(x,y,z))      
        end
    end
    output.clear()
    print("Creating image")
end

function draw()
    if ee then
        fill(255,0,0)
        text("Change a slider value then tap the screen to re-calc",WIDTH/2,HEIGHT-15)
        for a,b in pairs(tab) do
            setColor(b.z)
            ellipse(WIDTH/2+b.x*350,HEIGHT/2+b.y*350,6)
        end
        ee=false
        output.clear()                
    end
end

function touched(t)
    if t.state==BEGAN then
        calc()
    end
end

function setColor(c)
   if c<-.25 then
       fill(0,0,128+c*50)
   elseif c<0 then
       fill(0,0,255-c*300)
   elseif c<.0625 then
       fill(0,128+c*500,255-c*500)
   elseif c<.23 then
       fill(250-c*400, 193-c*400, 64-c*400, 255)
   elseif c<.4then
       fill(32+c*250,160+c*250,0)
   elseif c<.75 then
       fill(224-c*250,244-c*150,0)
   elseif c<.95 then
       fill(128+c*100,128+c*100,128+c*100,128+c*100)
   else
       fill(255,255,255,c*300)
   end
end

Oops, meant say programmatically how do you adjust brightness and contrast in an image.

Looks like brightening is just multiplying each colour variable by a percentage. Contrast looks very similar. I thought you would graduate a change over selected ranges in the image for contrast. Think I’ll use Gimp or Affinity!!!

@Bri_G The -1 to +1 is just the x and y ranges. Think of graph paper where the x and y axis goes from -1 to +1. Then I just increment through all the x and y axis values by .0075 . Then think of the z level as some amount above or below the graph paper. Every point at an x,y,z value will get a number back from the noise.perlin function. So every point in a cube will get some value from noise.perlin.

@Bri_G Here’s a cube where I calculate the perlin value for each value from -1.2 to 1.2 for x,y,z at increments of .2 . I then take the perlin value at those points and assign it a color value for each sphere. Use 2 fingers for pinch, expand, or move to zoom in, zoom out, or move the cube. It looks interesting to zoom in and rotate the cube. I also show color text that gives the color range of the spheres and the values. It takes several seconds for the color spheres to show.

supportedOrientations(LANDSCAPE_ANY)
displayMode(FULLSCREEN)

function setup()
    assert(craft, "Please include Craft as a dependency")
    assert(OrbitViewer, "Please include Cameras (not Camera) as a dependency")
    tab={}
    scene = craft.scene()
    skyMaterial=scene.sky.material
    skyMaterial.sky=color(0)
    skyMaterial.horizon=color(0)
    scene.sun.rotation=quat.eulerAngles(20,45,-30)
    v=scene.camera:add(OrbitViewer, vec3(0,0,0), 50, 0, 300)
    v.rx=10
    v.ry=20
    calc()
end

function createSphere(x,y,z,val)
    size=.2
    sphere1=scene:entity()
    s1=sphere1:add(craft.rigidbody,STATIC)
    sphere1.position=vec3(x*2,y*2,z*2)
    sphere1:add(craft.shape.sphere,size)
    sphere1.model = craft.model.icosphere(size,3)
    sphere1.material = craft.material("Materials:Specular")
    setColor(val)
    sphere1.material.diffuse=color(r,g,b)
    table.insert(tab,sphere1)
end

function setColor(v)
    if v<-.9 then
        r,g,b=0,0,0
    elseif v<-.6 then
        r,g,b=0,0,255
    elseif v<-.3 then
        r,g,b=0,255,0
    elseif v<0 then
        r,g,b=0,255,255
    elseif v>.9 then
        r,g,b=255,255,255
    elseif v>.6 then
        r,g,b=255,255,0
    elseif v>.3 then
        r,g,b=255,0,255
    elseif v>=0 then
        r,g,b=255,0,0
    end
end

function draw()
    update(DeltaTime)
    scene:draw()	
    text("Slide your finger to rotate image",WIDTH/2,HEIGHT-25)
    text("Two finger pinch or expand or move to zoom in or out or move.",WIDTH/2,HEIGHT-50)
    fill(94, 182, 229, 255)
    rect(0,425,80,200)
    fill(0,0,0)
    text("-1 to -.9",40,600)
    fill(0,0,255)
    text("-.9 to -.6",40,580)
    fill(0,255,0)
    text("-.6 to -.3",40,560)
    fill(0,255,255)
    text("-.3 to   0",40,540)
    fill(255,0,0)
    text("  0 to  .3",40,520)
    fill(255,0,255)
    text(" .3 to  .6",40,500)
    fill(255,255,0)
    text(" .6 to  .9",40,480)
    fill(255,255,255)
    text(" .9 to   1",40,460)
end

function update(dt)
    scene:update(dt)
end

function calc()
    tab={}
    step=.2
    n=craft.noise.perlin()
    --n=craft.noise.billow()
    for x=-1.2,1.2, step do
        for y=-1.2,1.2,step do
            for z=-1.2,1.2,step do
                value = n:getValue(x,y,z)
                createSphere(x*5,y*5,z*5,value)
            end
        end
    end
end