Perlin Noise routine

Blitz3D Forums/Blitz3D Programming/Perlin Noise routine

_PJ_(Posted 2015) [#1]
I have been working on this which is based on a code snippet from somewhere but I can't find the relevant source through searching.

I'm pretty sure it might have been Krischan because it's similar to his planet textuiring.

Anyway, it's not working right at all.

Const PerlinSamples=16
Const PerlinMapSize=64
Const PerlinSharpness#=0.5
Const SamplingLevel%=4

Dim SampleArray#(PerlinSamples-1,2)
Dim NoiseArray#(PerlinMapSize-1,PerlinMapSize-1,PerlinMapSize-1)

Function InitialisePerlin(Seed=0,Sharpness#=PerlinSharpness)
	If Not(Seed)
		Seed=MilliSecs()
	End If
	
	SeedRnd Seed
	
	Local X
	Local Y
	Local Z
	Local f#
	
	
	For X = 1 To PerlinMapSize
		For Y = 1 To PerlinMapSize
			For Z = 1 To PerlinMapSize
				f#=Rnd(0.000,1.000)
				NoiseArray#(X-1,Y-1,Z-1) = f
			Next
		Next
	Next
	
	Local Iterate
	Local SubIter
	
	; prepare octave Data
	For Iterate = 0 To PerlinSamples-1
		
		SampleArray#(Iterate,0) = 2 ^ Iterate
		SampleArray#(Iterate,1) = Sharpness# ^ Iterate
		SampleArray#(Iterate,2) = 0.0
		
		For SubIter = 0 To Iterate
			SampleArray#(Iterate,2)=SampleArray#(Iterate,2)+SampleArray#(SubIter,1)
		Next
		
		SampleArray#(Iterate,2) = 1.0 / SampleArray#(Iterate,2)
	Next
	
	Return Seed
	
End Function

Function Integer%(f#)
	If (f < 0 )
		Return Int(Ceil(f))
	End If
	Return Int(Floor(f))
End Function

Function PerlinNoisePoint#(X#,Y#,Z#,Resolution#=0.5)
	Local Baseline# = 0
	
	Local Octaves%=(Resolution*PerlinSamples)-1
	
	If Octaves < 0 Then Octaves = 0
	If Octaves => PerlinSamples Then Octaves = PerlinSamples-1
	
	Local Amplitude#
	Local Frequency#
	
	Local XStepsize
	Local YStepsize
	Local ZStepsize
	
	Local XBetween#
	Local YBetween#
	Local ZBetween#
	
	Local XInvBetween#
	Local YInvBetween#
	Local ZInvBetween#
	
	Local Iter%
	
	For Iter = 0 To Octaves%
		
		Frequency# = SampleArray#(Iter,0)
		Amplitude# = SampleArray#(Iter,1)
		
		XStepsize = Integer(X * Frequency#)
		YStepsize = Integer(Y * Frequency#)
		ZStepsize = Integer(Z * Frequency#)
		
		; get the inbetween co-ords
		XBetween# = EaseCurve((X * Frequency#) - XStepsize)
		YBetween# = EaseCurve((Y * Frequency#) - YStepsize)
		ZBetween# = EaseCurve((Z * Frequency#) - ZStepsize)
		
		XInvBetween# = 1 - XBetween#
		YInvBetween = 1 - YBetween#
		ZInvBetween# = 1 - ZBetween#
		
		Baseline#=Baseline#+PerlinVector(XStepsize,YStepsize,ZStepsize) * XInvBetween# * YInvBetween# * ZInvBetween#
		Baseline#=Baseline#+PerlinVector(XStepsize+1,YStepsize,ZStepsize) * XBetween# * YInvBetween# * ZInvBetween#
		Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize) * XInvBetween# * YBetween# * ZInvBetween#
		Baseline=Baseline+PerlinVector(XStepsize,YStepsize,ZStepsize+1) * XInvBetween# * YInvBetween# * ZBetween#
		Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize,ZStepsize+1) * XBetween# * YInvBetween# * ZBetween#
		Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize+1,ZStepsize) * XBetween# * YBetween# * ZInvBetween#
		Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize+1) * XInvBetween# * YBetween# * ZBetween#
		Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize+1) * XInvBetween# * YBetween# * ZBetween#
		Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize+1,ZStepsize+1) * XBetween# * YBetween# * ZBetween#
		
		Stop
		Baseline=Baseline*Amplitude
		
	Next
	
	Baseline# = Baseline# * SampleArray#(Octaves,2)
	
	Return Baseline#
End Function

Function PerlinVector#(X,Y,Z)
	If X < 0 Then X = X - (Integer((X / PerlinMapSize) - 1) * PerlinMapSize) Else X = X - (Integer(X/PerlinMapSize) * PerlinMapSize)
	If Y < 0 Then Y = Y - (Integer((Y / PerlinMapSize) - 1) * PerlinMapSize) Else Y = Y - (Integer(Y/PerlinMapSize) * PerlinMapSize)
	If Z < 0 Then Z = Z - (Integer((Z / PerlinMapSize) - 1) * PerlinMapSize) Else Z = Z - (Integer(Z/PerlinMapSize) * PerlinMapSize)
	
	Return NoiseArray#(X,Y,Z)
End Function

Function EaseCurve#(Vector#)
	Return(1-(Cos(Vector#)* 180.0)) * 0.5
End Function

Function GenerateNoisemap(Size=512, Sharpness#=PerlinSharpness#)
	Local MinimumSample =0
	
	Local xSize# = Size
	Local ySize# = Size*0.5
	
	Local xScale# = 360.0 / xSize
	Local yScale# = 180.0 / ySize
	
	Local pxs% = xSize-1
	Local pys% = ySize-1

	Local xof% = 0
	Local yof% = 0
	
	Local Image=CreateImage(xSize,ySize)
	Local Buffer=ImageBuffer(Image)
	
	Local posx
	Local posy
	
	Local xPolar#
	Local yPolar#
	Local zPolar#
	
	Local Component
	Local ComponentRatio#
	Local RGB
	
	Local ComponentMax=0
	Local ComponentMin=255
	
	Local X#
	Local Y#
	Local Z#
	
	LockBuffer Buffer
	
	;First Pass to obtain maxima and minima
	For posx% = 0 To pxs Step 1
		For posy% = 0 To pys Step 1
			
			
			;PolarX
			xPolar# = Cos((posx + xof + 0.5) * xScale#)
			zPolar# = Sin((posx + xof + 0.5) * xScale#)
			
			;PolarY
			yPolar# = Sin(((posy + yof + 0.5) * yScale#)) * 2
			
			;Determine Coordinates
			X# = (xPolar# * yPolar#) + 5
			Y# = (Cos(((posy + yof +  0.5) * yScale#)) * 2) + 5
			Z# = (zPolar# * yPolar#) + 5
				
			Component = PerlinNoisePoint(X#,Y#,Z#,Sharpness#)* 255.0
			Stop
			; cap the value
			If Component => 255 Then Component = 255
			If Component<= 0 Then Component = 0
				
			If Component<ComponentMin 
				ComponentMin=Component
			End If
			
			If Component>ComponentMax 
				ComponentMax=Component
			End If
			
			RGB=RGBa(Component,Component,Component)
			
			WritePixelFast posx,posy,RGB,Buffer
			
		Next
	Next
	
		;Second Pass to Normalise values
	For posx = 0 To pxs Step 1
		For posy = 0 To pys Step 1
			
			RGB=ReadPixelFast(posx,posy,Buffer)
			ComponentRatio#=Float((RGB And 255)/255.0)
			
			Component=Normalise(ComponentRatio,ComponentMin,ComponentMax)*255
			
			RGB=RGBa(Component,Component,Component)
			
			WritePixelFast posx,posy,RGB,Buffer
			
		Next
	Next
	
	UnlockBuffer Buffer
	
	Return Image
	
End Function

Function Normalise#(f#,Min#=0.0,Max#=1.0)
	Local Range#=(Max-Min)
	Local Normal#=(f-Min)/Range
	Return Normal
End Function

Function RGBa(R,G,B,a=255)
	Return (R And 255)+((G And 255) Shl 8)+((B And 255) Shl 16)+((a And 255) Shl 24)
End Function

Function IntToFloat#(n%)
	Local f#=n*1.0
	Return f#
End Function


I think the issue is with the Baseline value from the PerlinNoisePoint function. The value returned ought to be I think in a range of 0 - 1 but seems to be increased way out of bounds.

If anyone who has a better idea of the Perlin Noise concept or even otherwise that can help me out, I'd be extremely grateful!


Floyd(Posted 2015) [#2]
Too much to wade through, but this looks suspicious...

		; get the inbetween co-ords
		XBetween# = EaseCurve((X * Frequency#) - XStepsize)
		YBetween# = EaseCurve((Y * Frequency#) - YStepsize)
		ZBetween# = EaseCurve((Z * Frequency#) - ZStepsize)
		
		XInvBetween# = 1 - XBetween#
		YInvBetween = 1 - YBetween#
		ZInvBetween# = 1 - ZBetween#

That gives the impression Between and InvBetween are supposed to be complementary. They should be in the range 0 to 1 and their sum should be 1.

But then we have

Function EaseCurve#(Vector#)
	Return(1-(Cos(Vector#)* 180.0)) * 0.5
End Function


Note that as the angle goes from 0 to 180 degrees the Cos() will go 1 to -1.
That means 1-Cos() goes 0 to 2, so that ( 1 - Cos() ) * 0.5 goes 0 to 1.

That strongly suggests that Vector# is meant to be in the range 0 to 1 and the returned value should be

Return( 1 - Cos( Vector# * 180.0 ) ) * 0.5



_PJ_(Posted 2015) [#3]
You're right in sod far as the complimentarity and ranges for those variables.

I changed the EaseCurve function as suggested - thank you for catching that! - it definitely has helped and all seems to be within boundaries now, however there's still a problem - Baseline is now way too small, typically resulting around 0.0025 which when scaled up as a pixel colour component is only just about capable of rounding up to 1.
I don't believe this is an aspect of the randomnisity of the perlin routine, because every value is too low.

I think I shall get some sleep and try again in the morning - thanks for your help though!


Floyd(Posted 2015) [#4]
I had another quick look to see if anything felt wrong, despite not really understanding what is being done.

		Baseline#=Baseline#+PerlinVector(XStepsize,YStepsize,ZStepsize) * XInvBetween# * YInvBetween# * ZInvBetween#
		Baseline#=Baseline#+PerlinVector(XStepsize+1,YStepsize,ZStepsize) * XBetween# * YInvBetween# * ZInvBetween#
		Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize) * XInvBetween# * YBetween# * ZInvBetween#
		Baseline=Baseline+PerlinVector(XStepsize,YStepsize,ZStepsize+1) * XInvBetween# * YInvBetween# * ZBetween#
		Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize,ZStepsize+1) * XBetween# * YInvBetween# * ZBetween#
		Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize+1,ZStepsize) * XBetween# * YBetween# * ZInvBetween#
		Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize+1) * XInvBetween# * YBetween# * ZBetween#
		Baseline=Baseline+PerlinVector(XStepsize,YStepsize+1,ZStepsize+1) * XInvBetween# * YBetween# * ZBetween#
		Baseline=Baseline+PerlinVector(XStepsize+1,YStepsize+1,ZStepsize+1) * XBetween# * YBetween# * ZBetween#


It's clear from the variations on Stepsize+1 that this is meant to operate on the eight corners of a unit cube. But there are nine lines of code. Lines 7 and 8 are identical.

This is supposed to be a weighted average of eight values. Here's how such averages work.

Consider the 1D case, two values. You have weights A,B and values v1,v2. The weights must be non-negative and add up to 1.
The weighted average is A*v1 + B*v2. If both weights are 0.5 then you have an ordinary average of v1 and v2. But in any case you get a value between v1 and v2.

In 2D there are four weights, call them A,B,C,D. Again they are non-negative and the sum is 1.
The weighted average has the form A*v1 + B*v2 + C*v3 + D*v4.

3D has eight weights and eight values. You can guess how it works.

This sort of weighted average always has non-negative weights which sum to 1. It is called a convex combination. The result is always something between the largest and smallest of the values being averaged.

Your eight weights will have this property if the three X,Y,Z values are in the range 0 to 1 and the "Inv" values are the complements, i.e. XInv = 1 - X etc., which means they are also 0 to 1.


_PJ_(Posted 2015) [#5]
Sorry to have wasted your time, Floyd - you really have been very helpful, but I just couldn't get it to work correctly.

The code just seems too complicated in itself, despite the theory should be fairly straightforward.

As such, I decided to scrap it altogether and just write my own, where at least I can follow what's going on!

http://www.blitzbasic.com/codearcs/codearcs.php?code=3221


Flanker(Posted 2015) [#6]
Hello, I've written a perlin noise routine a while back. I just put it in the code archive, it can maybe be useful : http://www.blitzbasic.com/codearcs/codearcs.php?code=3223