Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Perlin noise for plotting n coordinates?

in Questions Posts: 1,795
I want to randomly plot an arbitrary number of x,y coordinates, but I don’t want them to look all “white noise”-y, I want them to look nice and clumpy like perlin noise does.

I guess I’m basically trying to do some kind of perlinPlotter function such that:

for i = 1, n do
newRandomPosition = perlinPlotter(i)
end

Is there a way to use the perlin noise generator to do that?

P.S. everything I’ve tried to do so far with the perlin noise generator so far just returns 0, so I must be getting something radically wrong.
Tagged:

Comments

  • dave1707dave1707 Mod
    Posts: 9,977

    @UberGoober Perlin noise kind of creates random values, but those random values are the same for the same x,z position when run. So each time you use it, you get the same y value for the same x,z . Also, you’ll always get a y value of zero if the x and z values are integers. To get other than zero, you need to use fractional positions for x and z. Run the below code with step 1 so the x,z values are integers. It will print just 0 for y. Change the step to .5 . At the half x,z values you’ll get some value for y. So if you want a lot of different randomness, you need to do small values for step starting and ending at some non integer value for x,z.

    Here’s an example for perlin noise.

    viewer.mode=STANDARD
    
    function setup()
        val=2
        step=1
        h=craft.noise.perlin()
        for x=-val,val,step do
            for z=-val,val,step do           
                y=h:getValue(x,0,z)
                print("x= "..x.." z= "..z.." y= "..y)
            end
        end
    end
    
  • Posts: 1,795

    @dave1707 I tried to apply your code to drawing an entire screen and it worked really well except for one puzzling thing.


    function setup() --set up initial variables p = craft.noise.perlin() perlinHigh, perlinLow = 0, 0 colorTable = {} --find increments that turn WIDTH and HEIGHT into ranges 0-1 stepX = 1/WIDTH stepY = 1/HEIGHT --using those, get a Perlin z value for every x, y for x = 0, 1, stepX do for y = 0, 1, stepY do z = p:getValue(x, y, 0) z = math.abs(z) table.insert(colorTable, z) if z > perlinHigh then perlinHigh = z elseif z < perlinLow then perlinLow = z end end end --turn the range of perlin values into a fraction of 255 perlinRange = perlinHigh - perlinLow colorMultiplier = 255 / perlinRange --use that fraction to turn each value into a color value for i, decimal in ipairs(colorTable) do rgbVal = decimal * colorMultiplier colorTable[i] = color(rgbVal, rgbVal, rgbVal, rgbVal) end --make an image and context perlinImage = image(WIDTH, HEIGHT) setContext(perlinImage) --draw each color to that image at the right place indexCount = 0 for x = 1, WIDTH do for y = 1, HEIGHT do indexCount = indexCount + 1 perlinImage:set(x, y, colorTable[indexCount]) end end end function draw() background(67, 172, 236) sprite(perlinImage, WIDTH/2, HEIGHT/2) end

    It works perfectly when run on my iPhone in landscape mode, but when run in portrait mode there’s a weird slash at the lower right (see images).

  • dave1707dave1707 Mod
    Posts: 9,977

    @UberGoober Your width height sizes don’t match the color table size, so you’re off each iteration thru the for loops. Make the change below. I increased the width and height by 1.

        --draw each color to that image at the right place
        indexCount = 0
        for x = 1, WIDTH+1 do
            for y = 1, HEIGHT+1 do     
                indexCount = indexCount + 1   
                perlinImage:set(x, y, colorTable[indexCount])
            end
        end
    
  • dave1707dave1707 Mod
    Posts: 9,977

    @UberGoober Heres another change to allow shifting of where the perlin noise starts to give a different view. Change the values of xOffset,yOffset to get different views.

        --using those, get a Perlin z value for every x, y
        xOffset,yOffset=10,30
        for x = 0, 1, stepX do
            for y = 0, 1, stepY do
                z = p:getValue(x+xOffset, y+yOffset, 0)
                z = math.abs(z)
                table.insert(colorTable, z)
                if z > perlinHigh then
                    perlinHigh = z
                elseif z < perlinLow then
                    perlinLow = z 
                end                
            end
        end
    
  • Posts: 1,795

    @dave1707 thanks for your help. I found that your offset-by-one code throws an out of range error unless amended to:


    for x = 1, WIDTH+1 do for y = 1, HEIGHT+1 do indexCount = indexCount + 1 if colorTable[indexCount] then perlinImage:set(x, y, colorTable[indexCount]) end end end

    But even then, I find that it only works in portrait mode. Attached is an image of the output in landscape mode. Do I have to change the code depending on the orientation? That seems illogical.

  • Posts: 1,795

    @dave1707 I like your offset trick. By changing the offsets to random values it becomes a lovely little cloud generator. :)

  • dave1707dave1707 Mod
    Posts: 9,977

    @UberGoober I guess the problem is what device the code is running on because the step calculations are probably causing problems with different width and height values. Need to change the way things are calculated. Maybe instead of just a straight table of values, you need a multiple table for [x][y].

  • dave1707dave1707 Mod
    Posts: 9,977

    @UberGoober Maybe you need a for x=1,WIDTH for y=1,HEIGHT to do calculations. Then in those loops your calculations for perlin will be fractions of x+offset and y+offset and then save the value at [x][y] table position.

  • edited April 7 Posts: 1,795

    @dave1707 really? I mean, you’re probably right, but if the difference between it working in one direction and it working in another direction is just adding one or not, it just kind of logically seems like there must be a simpler fix than rewriting everything.

  • dave1707dave1707 Mod
    Posts: 9,977

    @UberGoober It’s not that it works in one direction and not another, your original code didn’t work right in any direction on my iPad. Here’s some code I put together real quick to give you an example of the for loops and the x,y table. You can add your code for the high and low z values and do your other calculations for the cloud.

    viewer.mode=FULLSCREEN
    
    function setup()    
        p=craft.noise.perlin()
        w,h=WIDTH,HEIGHT    
        tab={}
    
        xOffset,yOffset=12,15
    
        for x=1,w do
            tab[x]={}
            x1=x/w
            for y=1,h do
                tab[x][y]={}
                y1=y/h
                z=p:getValue(x1+xOffset,y1+yOffset,0)
                tab[x][y]=z
            end
        end 
    
        img=image(w,h)
        setContext(img)
        for x=1,w do
            for y=1,h do
                c=(tab[x][y]*255)//1
                img:set(x,y,c,c,c,255)
            end
        end
        setContext()    
    end
    
    function draw()
        background(0)
        sprite(img,WIDTH/2,HEIGHT/2)
    end
    
  • Posts: 1,795

    Well that works, no doubt, thanks! A couple things I’m curious about: why are you dividing the X and Y by the width and height, respectively? Is that to generate decimal values? And what does the operator // do?

  • dave1707dave1707 Mod
    Posts: 9,977

    @UberGoober Dividing by w and h gives me a fraction between 0 and 1 for the width and height loops. Then I use that fraction and add it to the xOffset, yOffset to give me the fractional values from those values. The // is an integer divide so the color value was always an integer otherwise I got an error. The way you did your color values is ok, I was just in a hurry and didn’t try to add all of your code into this.

  • Posts: 1,795

    Here’s the lovely cloud generator thanks to @dave1707:


    function setup() --set up initial variables p = craft.noise.perlin() perlinHigh, perlinLow = 0, 0 perlinTable = {} colorTable = {} --get a Perlin z value for every x, y xOffset,yOffset=math.random(10), math.random(30) for x = 1, WIDTH do perlinTable[x] = {} x1 = x/WIDTH for y = 1, HEIGHT do perlinTable[x][y] = {} y1 = y/HEIGHT z = p:getValue(x1+xOffset, y1+yOffset, 0) z = math.abs(z) perlinTable[x][y] = z if z > perlinHigh then perlinHigh = z elseif z < perlinLow then perlinLow = z end end end --turn the range of perlin values into a fraction of 255 perlinRange = perlinHigh - perlinLow colorMultiplier = 255 / perlinRange --use that fraction to turn each value into a color value for x, yTable in ipairs(perlinTable) do colorTable[x] = {} for y, decimal in ipairs(yTable) do rgbVal = decimal * colorMultiplier newColor = color(rgbVal, rgbVal, rgbVal, rgbVal) colorTable[x][y] = newColor end end --make an image perlinImage = image(WIDTH, HEIGHT) --draw each color to that image at the right place setContext(perlinImage) for x=1, WIDTH do for y=1, HEIGHT do perlinImage:set(x,y,colorTable[x][y]) end end setContext() end function draw() background(67, 172, 236) sprite(perlinImage, WIDTH/2, HEIGHT/2) end
  • edited April 7 Posts: 2,689
    @dave1707 @UberGoober - from what I remember generating these images requires images to be squares, so that there is no difference between the lengths of the axes. I think that's why ratioing the dimensions of the axes to 1 works.

    If you want to make bigger areas I used to modify the tiles to match, at the borders, by either inverting them or programming to blend in at the overlaps (seamless tiles).
  • dave1707dave1707 Mod
    edited April 7 Posts: 9,977

    @UberGoober Here's a version.

    PS. Made changes, added parameters.

    viewer.mode=STANDARD
    
    function setup() 
        parameter.integer("xVal",-20,20,0)   
        parameter.integer("yVal",-20,20,0) 
        parameter.action("cloud",cloud)  
        p=craft.noise.perlin()
        cloud()
    end
    
    function cloud()   
        local tab={}
        local xOffset,yOffset=xVal,yVal  
        local min,max=0,0    
        for x=1,WIDTH do
            tab[x]={}
            local x1=x/WIDTH
            for y=1,HEIGHT do
                tab[x][y]={}
                local y1=y/HEIGHT
                local z=p:getValue(x1+xOffset,y1+yOffset,0)
                if z>max then
                    max=z
                elseif z<min then
                    min=z
                end
                tab[x][y]=z
            end
        end
        local col=256/(max-min)    
        img=image(WIDTH,HEIGHT)
        setContext(img)
        for x=1,WIDTH do
            for y=1,HEIGHT do
                local c=(tab[x][y]+math.abs(min))*col
                img:set(x,y,color(c,c,c,c))
            end
        end
        setContext() 
    end
    
    function draw()
        background(33, 140, 204)
        sprite(img,WIDTH/2,HEIGHT/2)
    end
    
  • dave1707dave1707 Mod
    Posts: 9,977

    @UberGoober Here’s another version. This has a range parameter that will increase the cloud area from 1x1 to 10x10. If you just increase the range parameter by 1 value each time, you’ll notice that the image shrinks to the lower left corner as the range increases.

    viewer.mode=STANDARD
    
    function setup() 
        parameter.integer("xVal",-20,20,0)   
        parameter.integer("yVal",-20,20,0) 
        parameter.integer("range",1,10,1)
        parameter.action("cloud",cloud)  
        p=craft.noise.perlin()
        cloud()
    end
    
    function cloud()   
        local tab={}
        local xOffset,yOffset=xVal,yVal  
        local min,max=0,0    
        for x=1,WIDTH do
            tab[x]={}
            local x1=x/(WIDTH/range)
            for y=1,HEIGHT do
                tab[x][y]={}
                local y1=y/(HEIGHT/range)
                local z=p:getValue(x1+xOffset,y1+yOffset,0)
                if z>max then
                    max=z
                elseif z<min then
                    min=z
                end
                tab[x][y]=z
            end
        end
        local col=256/(max-min)    
        img=image(WIDTH,HEIGHT)
        setContext(img)
        for x=1,WIDTH do
            for y=1,HEIGHT do
                local c=(tab[x][y]+math.abs(min))*col
                img:set(x,y,color(c,c,c,c))
            end
        end
        setContext() 
    end
    
    function draw()
        background(33, 140, 204)
        sprite(img,WIDTH/2,HEIGHT/2)
    end
    
  • Posts: 1,795

    @dave1707 these are great.

    I noticed that even changing the X offset by one produces a dramatically different image, which surprises me, because I had assumed that gradual changes would result in gradual visual differences.

  • dave1707dave1707 Mod
    Posts: 9,977

    @UberGoober If you use my latest code and set the range to 10, when you change the x or y value by 1, you’ll see the image shift by 1/10 the image size. At a range of 1, the whole image is shifted by the whole screen size. I used to have some perlin code that covered a larger area so as you changed the x or y values, you would see the image scroll across the screen. I’m not sure where that is anymore.

  • Posts: 1,795

    It’s visible nice and smoothly when I use these parameters:


    parameter.number("xVal",-5,5,0, function() cloud() end) parameter.number("yVal",-5,5,0, function() cloud() end) parameter.integer("range",1,10,1, function() cloud() end)

    I wonder if the perlin calculations are fast enough to make that into an animation…

  • dave1707dave1707 Mod
    Posts: 9,977

    @UberGoober I guess it depends on the screen size. It doesn’t scroll on my iPad, I have to wait for the update each time. Maybe if my iPad was faster.

  • Posts: 1,795

    This version modularizes the cloud function so it can draw to any image supplied to it.

    It also animates the clouds, but verrrrry slowly, because if I tried to make it go any faster it looked really choppy.


    function setup() parameter.number("xVal",-5,5,0) parameter.number("yVal",-5,5,0) parameter.integer("thisRange",1,10,3) p=craft.noise.perlin() imageToUse = image(WIDTH, HEIGHT) spriteMode(CENTER) end function cloud(targetImg, xOffset, yOffset, range) local tab={} xOffset, yOffset = xOffset or 1, yOffset or 1 range = range or 1 local min,max=0,0 for x=1, targetImg.width do tab[x]={} local x1=x/(targetImg.width/range) for y=1,targetImg.height do tab[x][y]={} local y1=y/(targetImg.height/range) local z=p:getValue(x1+xOffset,y1+yOffset,0) tab[x][y]=z max = math.max(max, z) min = math.min(min, z) end end local col=256/(max-min) setContext(targetImg) for x=1, targetImg.width do for y=1, targetImg.height do local c=(tab[x][y]+math.abs(min))*col targetImg:set(x,y,color(c,c,c,c)) end end setContext() return targetImg end function draw() xVal = xVal + (ElapsedTime * 0.0003) yVal = yVal + (ElapsedTime * 0.0003) cloud(imageToUse, xVal, yVal, thisRange) background(33, 140, 204) sprite(imageToUse, WIDTH * 0.65, HEIGHT * 0.65, WIDTH / 1.75, HEIGHT/1.75) end
  • dave1707dave1707 Mod
    edited April 7 Posts: 9,977

    @UberGoober That scrolls better.

    PS. If I look out my windows right now, those are the kind of clouds I see.

Sign In or Register to comment.