Return of The Noise

Yeah, anything outside of ± 32,767 isn’t going to work. The compiler should warn/error for that, good idea! Why didn’t I think of that?!

1 Like

Ok, @zranger1 , I searched for PRNG and xorshift and both yielded hints of multiple things you’ve written (rule30, among others) but no links to them. Oh wait, found the rule30… But yeah, we need a PRNG library thread…

Given that cellnoise is basically a PRNG, we should replace that non16.16 stuff with one of those…
Where’s the code I can crib from?

added later, moved from the PRNG thread, as it’s more appropriate here:

So interestingly, playing with graphtoy, which similar to Tixy, allows you to use t as a time changing variable, and thus, most movement is t based. So using noise(t), or voronoi(t), or even sin(t), all of it is directly tied to the same variable. So that means if you call noise(t) in two places, they get the same value back. This is great if you want them synchronizing… But if you don’t, and you wish bits A and B to both be random noisy/moving but NOT obviously synced, you attempt to do tricks like use some odd variation of t (like 1/t), such that the eye doesn’t see the true hidden variable. (This has deep quantum thoughts about the true nature of free will… But besides that…)

So ideally you could have multiple random sources and call the same noise() using t but invoking noise source (PNRG) #2, (so noise(t, 2) which will give you a different result from source #1, even though they both use t. Different PRNGs yields (likely) different results even from same seed.

So worth doing… maybe worth doing 3+ times…

Added after for clarity for those reading later:

You need a PRNG (where you give it a seed x, and it returns the same result each time, consistent but change x slightly and you’ll get a different random number) as a noise source, so it can generate a smooth curve between two given values. So noise(x) yields some random set of values but smoothly between values of x. Random() yields just a range of values, so not smooth. Voronoi is more discrete, think of it as the triangle version of noise (if noise is considered as a wave)

So if I do two noise(x) waves, even though it’s going to randomly fluctuate, it will fluctuate exactly the same way for any given value of X.
So two noise(x) look identical. So the key is to use different PRNG sources and then noise(x,1) could be very different from noise(x,2).

Without a second PRNG, you end up faking it by doing things like noise(x * 2), or noise(x * 3) etc…
So that your input value of X looks different enough that it won’t match the first.

But if you use t (time) as a noise source (causing wiggles), using t * 2,for example, is changing twice as fast as t… So you can do t * 1.00001 but… that looks very similar to just 1… side by side, they move at the same speed. Having two+ PRNGs would be much cleaner.

Illustration might help here

Afterwards: I’m now beginning to understand that the noise seed is more a hash than a PRNG. It’s a spectrum, really… It doesn’t have to be as complex and “random” as a PRNG does.

There’s a fast, clean implementation of xorshift in @wizard’s “static random colors” pattern in the library. Here’s the relevant bit: (and I totally agree about an RNG/PRNG thread! If you don’t set one up, I will when I get home!)

// 16 bit xorshift from 
// http://www.retroprogramming.com/2017/07/xorshift-pseudorandom-numbers-in-z80.html
var xs
function xorshift() {
  xs ^= xs << 7
  xs ^= xs >> 9
  xs ^= xs << 8
  return xs
}

// return a pseudorandom value between 0 and 1
function pseudorandomFraction() {
  return xorshift() / 100 % 1
}

Still researching the original cellnoise.

Found what looks like a goof too.
Those numbers are supposed to all be prime.
1376312589 isn’t but 1376312579 is…

I suspect someone goofed but all of the examples of this sort of algo online use the wrong value, and even label it as prime.
Oops.

Those 3 large values are 16bit, 24bit, and 32bit. That they are prime is helpful, but I suspect it won’t matter for converting to 16.16.

If we convert them to 16.16, I believe the entire algo would work as expected which is basically trying to use multiplication and addition as bit manipulation.

But I can’t seem to find a quick and dirty 16.16 to decimal converter. I can get the binary for each easily (Google/duckduck work fine for “X in binary”). So I have the patterns but now I need to put into a value for PB, and I’m slightly lost. @wizard , any tips?

Oh, I get it now I think… I read http://hugi.scene.org/online/coding/hugi%2015%20-%20cmtadfix.htm

If I take those high values and divide by 65536, I think.
so

1376312579 / 65536 = 21000.863327
                     21000.863327026367 by calc below
Interestingly, the bit difference between these two 
                   is v 1101 versus 0011 ^ at the end 
the original # 1376312589 / 65536 = 21000.8634796
                                    21000.863479614258 by calc below
15731 / 65536 = 0.240036010742
                0.2400360107421875 by the calculator linked below
and 
789221 / 65536 =  12.0425567627
                  12.042556762695312 by calc

@Scruffynerf there’s no integer literal that can fill the lower 16 bits since those are fractional values.

You can of course shift integers right, but you are limited in how many bits you can use in a single integer literal. Also, right shifts have sign extension (negative numbers, like 0x8000 through 0xFFFF will fill with ones).

And unfortunately you can’t use hex with a period like decimal places. You can use a real number literal. Want the bit just to the right of 1? Add 1/2. Want the 2nd bit? 1/4, then 1/8, and so on. 1 / (2^n). If you go too far, know that the least significant bit in literals is dropped (16.15 bits is the literal literal limit), there is no way to get a value of 1/65536 in a literal with PB, though you can get that value through calculation.

For example, (0x1234ABCD) / 65536 == 4660.671096801757813

4660.671096801757813 once evaluated will get you a value of 0x1234.ABCC
The least significant bit is dropped and the D converted to a C, but otherwise you can use this to specify up to 16.15 bits.

Also, keep in mind some bitwise operators work on the top 16 bits only, and some work on all 32.

1 Like

Heh, I was just updating my post, see the values there, that look ok?

Found it. And my original “divide by 65536” approach was spot on, especially if we lose the last bit of precision anyway. I was able to take my binary pattern (Hey google, what’s 15731 in binary), add it as the last 16 (or 24 or all 32) bits, and get the result in decimal, which matched 15731/65536 almost exactly)

http://www.sunshine2k.de/coding/javascript/fp/sunfp_js.html

more at Sunshine's Homepage - Understanding and implementing fixed point numbers

Re the big “nonprime” number, I msged Inigo to ask if it was supposed to be 79 or what? And he replied that number has little importance, and it doesn’t need to be prime. “You can even remove it if you want, most likely”. One more data point toward getting this working.
He also said that cellnoise() is really a hash function and not really a PRNG. Will have to ponder that. Ahh…

https://peteroupc.github.io/random.html#Procedural_Noise_Functions

I understand more now.

The above led me to

https://www.redblobgames.com/articles/noise/introduction.html

Also good.

This rabbit hole has me firmly in it’s grasp… Learning a ton, and all so I can light up little dots of light in more interesting ways.

1 Like

Not much daylight between PRNGs and hashing functions. I’ve seen lots of people use xorshift-like things for hashing over the years. When traveling down the RNG/Noise/Crypto rabbit hole, which is a particularly thorny and contentious subfield, I’ve found it useful to read enough for a broad overview of the field so I’ll know generally what’s available, and when I’m in way over my head.

Then, I’ll dive deep and learn good tools for the specific task(s) I have in mind. There’s a world of difference between what you need to make decent looking displays and a super crypto-ready RNG that’s immune to any attack that doesn’t involve a quantum computer.

That reminds me, I’ve done a shuffle function for Pixelblaze to randomize pixel indices if anybody ever needs it. It’s the Fisher-Yates algorithm, by way of Knuth again…

(Edit: Moved shuffle code to random number thread which is where I thought I was when I typed it. :slight_smile: )

1 Like

This is all fantastic stuff. At some point, we’re going to need a wiki to keep it in!

1 Like

Hmm… Expression Doc says

Can you clarify? Should that read all 32 bits?
And besides ~, what’s the other exceptions?
No rush to document, just noting the discrepancy.

Difference between a hash and a PRNG (in my head, now):

Hash: takes value, returns a result, same every time.
PRNG: takes a seed value, returns a series of results, ideally the same results, given same starting seed. Once you input the seed, the PRNG can be used again without submitting a new seed, and the series continues.

The series should show the behavior/appearance of being random. Some PRNGs are better at this.

The hash should return a spread, but with only one result, the goal is get a “random” spread of values given different inputs. Some hash algos give you fairly unique results. This isn’t a requirement though. Two different input values can give you the same results even in md5, for example.

If you require a given seed for a PRNG to always give the same results, you can’t use extra local things like a changing time value, since running the same seed should always give you same results. If you just want random, everytime, regardless of seed, more of an RNG, you could use those sort of inputs too. With hashes, since the same input yield the same output, it’s always deterministic. So the line between hash and RNG is where we find the PRNGs… That’s the spectrum…

Going back to noise…

Awesome set of lessons on noise, using js.
The perlin noise loop cries out to be a PB pattern.

In fact, this channel seems packed with JS goodness.

Just spent a few hours playing with Noise again.

@jeff’s Perlin Noise 2D code works, and works decently, I got it work in my testing… but… it turns out that for a 2D display (like a led matrix), you really want 3D perlin noise, to make it look right. You can shift the ‘noise map’ around, but you can’t make it change itself smoothly, without a 3rd dimension. (So we can use t (time) in place of z, and that makes it actually evolve smoothly as you watch it)

So I dug into the code, and found something I’ll adapt into a self contained and hopefully fast version. Turns out that the massive p array in Jeff’s code (and most others, too), is a well mixed up set of values from 1-255 or so, then duplicated into 256-512, for avoiding array overrunning). If that array isn’t well mixed, the results are less mixed… but everyone is using the same ‘random’ set of values Ken Perlin used in his original reference code… because there is a small risk of not having a good mix… so everyone just uses a known list? Weird.
It also makes for ugly PB code, because we can’t just do array = [1,2,3…], we have to add each item one at a time.

Found a ‘quick’ and simple 3D perlin noise in JS, that among other things, allows you to build the array on the fly, so I’ll port that, and hopefully we’ll have something that works in the limited PB JS and also is truly more random too.

Update: ok, a few hours later… we have code

Perlin 3D noise
// Perlin 3D noise
//
// Written by Thom Chiovoloni, dedicated into the public domain 
// per http://creativecommons.org/publicdomain/zero/1.0
// https://github.com/thomcc/quick-noise.js
//
// adapted by ScruffyNerf into Pixelblaze's JSish language
// you could replace the use of random() with a different RNG if you wish

var arraySize = 256
var permSize = arraySize*2
var gradSize = permSize
var arr = array(arraySize)
var perm = array(permSize)
var grad1 = array(gradSize)
var grad2 = array(gradSize)
var grad3 = array(gradSize)
var gradBasis = array(36)
var gradIndex = 0
var gradIdx = 0

export var scale = 1
export function sliderScale(v){
  scale = 0.0001+(v*10)
}

function buildTable() {
		arr.mutate(arrayFill)
		arr.mutate(arrayShuffle)
		arr.mutate(arrayShuffle) // let's mix it up again, just to be sure, optional
		perm.mutate(permClone)
		grad(1,1,0); grad(-1,1,0); grad(1,-1,0); grad(-1,-1,0)
    grad(1,0,1); grad(-1,0,1); grad(1,0,-1); grad(-1,0,-1)
    grad(0,1,1); grad(0,-1,1); grad(0,1,-1); grad(0,-1,-1)
    perm.forEach(gradPop)
}

function arrayFill(v,i,a){
  return i	  
}

function arrayShuffle(v,i,a){
  r = floor(random(a.length))
  t = arr[r]
  arr[r] = v
  return t
}

function permClone(v,i,a){
  if (i > arraySize -1) {
    return arr[i-arraySize]
  } else {
    return arr[i]
  }
}

function grad(x, y, z) {
  gradBasis[gradIndex] = x
  gradIndex++
  gradBasis[gradIndex] = y
  gradIndex++
  gradBasis[gradIndex] = z
  gradIndex++
}

function gradPop(v,i,a){
  g = v%12 * 3
  grad1[gradIdx] = gradBasis[g]
  grad2[gradIdx] = gradBasis[g+1]
  grad3[gradIdx] = gradBasis[g+2]
  gradIdx++
}

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

buildTable()

function noise(x, y, z, xWrap, yWrap, zWrap) {
	// x, y, z are numbers.
	// xWrap, yWrap, and zWrap are integer powers of two between 0 and 256.
	// (0 and 256 are equivalent). If these aren't provided, they default to 0.
	
  xMask = ((xWrap-1) & 255) >> 0
	yMask = ((yWrap-1) & 255) >> 0
	zMask = ((zWrap-1) & 255) >> 0

	px = floor(x)
  py = floor(y)
	pz = floor(z)

	x0 = (px+0) & xMask
	x1 = (px+1) & xMask

	y0 = (py+0) & yMask
	y1 = (py+1) & yMask

	z0 = (pz+0) & zMask
	z1 = (pz+1) & zMask

	x -= px
	y -= py
	z -= pz

	u = fade(x)
	v = fade(y)
	w = fade(z)

	r0 = perm[x0]
	r1 = perm[x1]

	r00 = perm[r0+y0]
	r01 = perm[r0+y1]
	r10 = perm[r1+y0]
	r11 = perm[r1+y1]

	h000 = perm[r00+z0]
	h001 = perm[r00+z1]
	h010 = perm[r01+z0]
	h011 = perm[r01+z1]
	h100 = perm[r10+z0]
	h101 = perm[r10+z1]
	h110 = perm[r11+z0]
	h111 = perm[r11+z1]

	n000 = grad1[h000]*(x+0) + grad2[h000]*(y+0) + grad3[h000]*(z+0)
	n001 = grad1[h001]*(x+0) + grad2[h001]*(y+0) + grad3[h001]*(z-1)
	n010 = grad1[h010]*(x+0) + grad2[h010]*(y-1) + grad3[h010]*(z+0)
	n011 = grad1[h011]*(x+0) + grad2[h011]*(y-1) + grad3[h011]*(z-1)
	n100 = grad1[h100]*(x-1) + grad2[h100]*(y+0) + grad3[h100]*(z+0)
	n101 = grad1[h101]*(x-1) + grad2[h101]*(y+0) + grad3[h101]*(z-1)
	n110 = grad1[h110]*(x-1) + grad2[h110]*(y-1) + grad3[h110]*(z+0)
	n111 = grad1[h111]*(x-1) + grad2[h111]*(y-1) + grad3[h111]*(z-1)

	n00 = n000 + (n001-n000) * w
	n01 = n010 + (n011-n010) * w
	n10 = n100 + (n101-n100) * w
	n11 = n110 + (n111-n110) * w

	n0 = n00 + (n01-n00) * v
	n1 = n10 + (n11-n10) * v

	return n0 + (n1-n0) * u
}

var time
export function beforeRender(delta) {
  time += delta/1000
}

export function render2D(index, x, y) {
  hsv(noise(x*scale, y*scale,time), 1, 1)
}

Not bad, runs at 17.5 frames a second on 16x16 matrix, v3 PB.

Ran into the array size limit, trying to make a 512x3 array, so I broke it into 3 arrays of 512. (This uses 1 256, 4 512s, and a 32, in addition to whatever your pattern uses)
There is supposed to be a way to make this hashable, and not use all of these lookups, that might be only way to speed this up, if hash calcing is faster than array lookups, of which this uses many)

@wizard , consider this a request to push something like this (or better) into firmware, a noise() function is pretty critical for many fun things. Needs to be at least 3d noise (and with the awesome level of 3D mapping we have otherwise in PB, really 4D noise might be worthwhile if it’s doable)

Tweaks before I add it to the library…

  1. I can make it fall back to the ‘known’ array, which should guarantee the same results every time, assuming you don’t randomize otherwise (ie given the same map, it’ll look identical every pattern run then)
  2. cleanup and add something nicer than just a full map noise display. I did add a slider for scale, but really it should be a scale on all 3 axis, so you could distort X and Y, and also change the time scaling/speed.
  3. In other code, moving the origin center to the middle of the map (.5,.5) is nice for other reasons of math, but noise likes positive numbers, so that correcting might be worth putting in as part of the function. abs()ing would be weird (and mirror), so maybe just a global offset?
  4. If you want two different noise fields, right now you can’t do that. Wondering if there is a way around that… Might just have to do a large offset instead, because of the array overhead.
  5. Sigh, probably Simplex is needed, as faster, scales better too.

Added: and guess what, it won’t run on a PB v2 because it runs out of array space.

(My dev v3 blew up (went into setup and won’t seem to wake up despite reflashing it), so I grabbed a handy v2, and learned this the hard way. On the bright side, I’m going to try a Value Noise implementation I found, and see how it looks.

Value Noise code - same pattern as above roughly but different
var seed = random(100)

function lerp(a, b, t) {
		return (1 - t) * a + t * b;
}

function hash2d(x, y) {
  x = 50 * frac(x * 0.3183099 + 0.71);
	y = 50 * frac(y * 0.3183099 + 0.113);
	return -1 + 2 * frac(1.375986 * seed + x * y * (x + y));
}

function hash3d(x, y, z) {
	x = 50 * frac(x * 0.3183099 + 0.71);
	y = 50 * frac(y * 0.3183099 + 0.113);
	z = 50 * frac(z * 0.3183099 + 0.33);
  return -1 + 2 * frac(1.375986 * seed + x * y * z * (x + y + z));
}

function noise2d(x, y) {
			ix = floor(x);
			iy = floor(y);
			fx = frac(x);
			fy = frac(y);
			ux = fx * fx * (3 - 2 * fx);
			return lerp(
				lerp(hash2d(ix, iy), hash2d(ix + 1, iy), ux),
				lerp(hash2d(ix, iy + 1), hash2d(ix + 1, iy + 1), ux),
				fy * fy * (3 - 2 * fy)
			);
}

function noise3d(x, y, z){
			ix = floor(x);
			iy = floor(y);
			iz = floor(z);
			fx = frac(x);
			fy = frac(y);
			fz = frac(z);
			ux = fx * fx * (3 - 2 * fx);
			uy = fy * fy * (3 - 2 * fy);
			return lerp(
				lerp(
					lerp(hash3d(ix + 0, iy + 0, iz + 0), hash3d(ix + 1, iy + 0, iz + 0), ux),
					lerp(hash3d(ix + 0, iy + 1, iz + 0), hash3d(ix + 1, iy + 1, iz + 0), ux),
					uy
				),
				lerp(
					lerp(hash3d(ix + 0, iy + 0, iz + 1), hash3d(ix + 1, iy + 0, iz + 1), ux),
					lerp(hash3d(ix + 0, iy + 1, iz + 1), hash3d(ix + 1, iy + 1, iz + 1), ux),
					uy
				),
				fz * fz * (3 - 2 * fz)
			);
}

function fractal2d(x, y, octaves) {
			val = 0;
			for (i = 0; i < octaves; i++) {
				val += noise2d(x, y) / pow(2, 0.5 + i - 0.5 * i);
				x -= i * 19;
				y += i * 7;
				x *= 1.57;
				y *= 1.57;
			}
			return val;
}

function fractal3d(x, y, z, octaves) {
			val = 0;
			for (i = 0; i < octaves; i++) {
				val += noise3d(x, y, z) / pow(2, 0.5 + i - 0.5 * i);
				x -= i * 7;
				y += i * 13;
				z -= i * 23;
				x *= 1.57;
				y *= 1.57;
				z *= 1.57;
			}
			return val;
}

export var noisescale = 1
export function sliderScale(v){
  noisescale = v*10
}

var time
export function beforeRender(delta) {
  time += delta/5000
}

export function render2D(index, x, y) {
  hsv(noise3d(x*noisescale, y*noisescale, time), 1, 1)
}

Run at 10.5 frames on my v2… so it should be faster on a v3 (I need to solder one up until we figure out how to recover my other one. I also have a pile of Picos to be soldered.)

Value vs Perlin? They are different, but might be an acceptable substitute in some cases.

And minutes later, realizing I could solve the array usage, you don’t need double sized arrays, if you just mod results to 256, I have Perlin Noise working on the v2, at 12fps…

v2 compatible Perlin - now with smaller arrays!
// Perlin 3D noise (now with even more reduced array usage!)
//
// Written by Thom Chiovoloni, dedicated into the public domain 
// per http://creativecommons.org/publicdomain/zero/1.0
// https://github.com/thomcc/quick-noise.js
//
// adapted by ScruffyNerf into Pixelblaze's JSish language
// you could replace the use of random() with a different RNG if you wish

var arraySize = 256
var permSize = arraySize
var gradSize = permSize
var perm = array(permSize)
var grad1 = array(gradSize)
var grad2 = array(gradSize)
var grad3 = array(gradSize)
var gradBasis = array(36)
var gradIndex = 0
var gradIdx = 0


function buildTable() {
		perm.mutate(arrayFill)
		perm.mutate(arrayShuffle)
		perm.mutate(arrayShuffle) // for good measure, shuffle twice
		grad(1,1,0); grad(-1,1,0); grad(1,-1,0); grad(-1,-1,0)
    grad(1,0,1); grad(-1,0,1); grad(1,0,-1); grad(-1,0,-1)
    grad(0,1,1); grad(0,-1,1); grad(0,1,-1); grad(0,-1,-1)
    perm.forEach(gradPop)
}

function arrayFill(v,i,a){
  return i	  
}

function arrayShuffle(v,i,a){
  r = floor(random(a.length))
  t = a[r]
  a[r] = v
  return t
}

function grad(x, y, z) {
  gradBasis[gradIndex] = x
  gradIndex++
  gradBasis[gradIndex] = y
  gradIndex++
  gradBasis[gradIndex] = z
  gradIndex++
}

function gradPop(v,i,a){
  g = v%12 * 3
  grad1[gradIdx] = gradBasis[g]
  grad2[gradIdx] = gradBasis[g+1]
  grad3[gradIdx] = gradBasis[g+2]
  gradIdx++
}

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

buildTable()

function noise(x, y, z, xWrap, yWrap, zWrap) {
	// x, y, z are numbers.
	// xWrap, yWrap, and zWrap are integer powers of two between 0 and 256.
	// (0 and 256 are equivalent). If these aren't provided, they default to 0.
	
  xMask = ((xWrap-1) & 255) >> 0
	yMask = ((yWrap-1) & 255) >> 0
	zMask = ((zWrap-1) & 255) >> 0

	px = floor(x)
  py = floor(y)
	pz = floor(z)

	x0 = (px+0) & xMask
	x1 = (px+1) & xMask

	y0 = (py+0) & yMask
	y1 = (py+1) & yMask

	z0 = (pz+0) & zMask
	z1 = (pz+1) & zMask

	x -= px
	y -= py
	z -= pz

	u = fade(x)
	v = fade(y)
	w = fade(z)

	r0 = perm[x0%256]
	r1 = perm[x1%256]

	r00 = perm[(r0+y0)%256]
	r01 = perm[(r0+y1)%256]
	r10 = perm[(r1+y0)%256]
	r11 = perm[(r1+y1)%256]

	h000 = perm[(r00+z0)%256]%256
	h001 = perm[(r00+z1)%256]%256
	h010 = perm[(r01+z0)%256]%256
	h011 = perm[(r01+z1)%256]%256
	h100 = perm[(r10+z0)%256]%256
	h101 = perm[(r10+z1)%256]%256
	h110 = perm[(r11+z0)%256]%256
	h111 = perm[(r11+z1)%256]%256

	n000 = grad1[h000]*(x+0) + grad2[h000]*(y+0) + grad3[h000]*(z+0)
	n001 = grad1[h001]*(x+0) + grad2[h001]*(y+0) + grad3[h001]*(z-1)
	n010 = grad1[h010]*(x+0) + grad2[h010]*(y-1) + grad3[h010]*(z+0)
	n011 = grad1[h011]*(x+0) + grad2[h011]*(y-1) + grad3[h011]*(z-1)
	n100 = grad1[h100]*(x-1) + grad2[h100]*(y+0) + grad3[h100]*(z+0)
	n101 = grad1[h101]*(x-1) + grad2[h101]*(y+0) + grad3[h101]*(z-1)
	n110 = grad1[h110]*(x-1) + grad2[h110]*(y-1) + grad3[h110]*(z+0)
	n111 = grad1[h111]*(x-1) + grad2[h111]*(y-1) + grad3[h111]*(z-1)

	n00 = n000 + (n001-n000) * w
	n01 = n010 + (n011-n010) * w
	n10 = n100 + (n101-n100) * w
	n11 = n110 + (n111-n110) * w

	n0 = n00 + (n01-n00) * v
	n1 = n10 + (n11-n10) * v

	return n0 + (n1-n0) * u
}

export var noisescale = 1
export function sliderScale(v){
  noisescale = v*10
}

var time
export function beforeRender(delta) {
  time += delta/5000
}

export function render2D(index, x, y) {
  hsv(noise(x*noisescale, y*noisescale, time), 1, 1)
}

If you run both, you can easily see the difference in Value and Perlin… Value is much less smooth, it’s ‘peaky’, no matter how you scale it… Perlin absolutely is the winner for natural flow.

And in the midst of porting Flow my Tears, and running out of array space again, I just realized I no longer need to duplicate the built array into the perm array, which will save even more space. Revised code is now above.

3 Likes

See above for updates

I’ll absolutely be running/writing more code on v2s now, it helps to improve the code when you have to make it work within more limited scope. Kudos to @wizard for bringing v2 up to v3 language specs. Without it, I’m sure I’d have not bothered.

Ok, I just ported a different version of noise code, with both perlin and simplex for 2d and 3D, and I finally feel like I can say “OH, I get it now.” At least enough to see how to optimize the code better… And I have 2 different perlin3d functions which basically do the same thing at different speeds, so now I need to figure out how to optimize it a bit more. Right now, the 2 functions using the same source data, give different results, need to figure out why.

Updated: Order of operations on multiple lookups thru multiple tables matters, references changing references in arrays, AND also was lerping one in a different order… Having 2 different working instances, probably either would work, but having both running, it was clear that they were vastly different. It was like comparing XYZ to ZYX… now I’m looking for the fastest method of doing the math… reducing mods, and so on. Now the identical function results RUN at different frame rates… one is 9fps, the other is 11fps (on a v2) 2 fps purely cause is better at the math… but which math?

I now have one version consistently slightly faster… 11.72 vs 11.64 (and it was the slower one I got going faster, as I moved things from one to the other…)

And then… it’s onward to 4D noise… a stretch goal for sure.

1 Like
Non-table simplex noise - SO SLOW??!!
// Simplex, ported from https://github.com/jackunion/tooloud

var i, j, k;
var A = array(3)
A[0] = 0
A[1] = 0
A[2] = 0

var u, v, w;
var T = array(8)
T[0] = 0x15
T[1] = 0x38
T[2] = 0x32
T[3] = 0x2c
T[4] = 0x0d
T[5] = 0x13
T[6] = 0x07
T[7] = 0x2a

export var b2loop, b232k, b4loop, b432k, kloop, k32k, shufloop, shuf32k
function b2func(N, B) {
        b2loop++
        if (b2loop > 32000){ b2loop = 0; b232k++ }
        return N >> B & 1;
}

function b4func(i, j, k, B) {
        b4loop++
        if (b4loop > 32000){ b4loop = 0; b432k++ }
        return T[b2func(i, B) << 2 | b2func(j, B) << 1 | b2func(k, B)];
    }

function K(a) {
        kloop++
        if (kloop > 32000){ kloop = 0; k32k++ }
        s = (A[0]+A[1]+A[2]) / 6.;
        x = u - A[0] + s
        y = v - A[1] + s
        z = w - A[2] + s
        t = .6 - x * x - y * y - z * z;
        h = shuffle(i + A[0], j + A[1], k + A[2]);

        A[a]++;

        if (t < 0) return 0;

        b5 = h >> 5 & 1
        b4 = h >> 4 & 1
        b3 = h >> 3 & 1
        b2 = h >> 2 & 1
        b = h & 3;
        p = (b == 1) ? x : (b == 2) ? y : z
        q = (b == 1) ? y : (b == 2) ? z : x
        r = (b == 1) ? z : (b == 2) ? x : y;
        p = (b5 == b3 ? -p : p);
        q = (b5 == b4 ? -q : q);
        r = (b5 != (b4^b3) ? -r : r);
        t *= t;

        return 8 * t * t * (p + (b == 0 ? q + r : b2 == 0 ? q : r));
    }

function shuffle(i, j, k) {
  shufloop++
  if (shufloop > 32000){ shufloop = 0; shuf32k++ }
  return b4func(i, j, k, 0) + b4func(j, k, i, 1) + b4func(k, i, j, 2) + b4func(i, j, k, 3) +
        b4func(j, k, i, 4) + b4func(k, i, j, 5) + b4func(i, j, k, 6) + b4func(j, k, i, 7);
}

export var seedValue = random(3000)

function noise(a, b, c) {
        x = a + seedValue;
        y = b + seedValue;
        z = c + seedValue;
        s = (x + y + z) / 3;

        i = floor(x + s);
        j = floor(y + s);
        k = floor(z + s);
        s = (i + j + k) / 6.;
        u = x - i + s;
        v = y - j + s;
        w = z - k + s;
        A[0] = A[1] = A[2] = 0;

        hi = u>=w ? u>=v ? 0 : 1 : v>=w ? 1 : 2;
        lo = u< w ? u< v ? 0 : 1 : v< w ? 1 : 2;

        return ((K(hi) + K(3 - hi - lo) + K(lo) + K(0))*3.2);
}

export var noisescaleX = 1
export function sliderScaleX(v){
  noisescaleX = v*10
}

export var noisescaleY = 1
export function sliderScaleY(v){
  noisescaleY = v*10
}

export var noisescaleZ = 1
export function sliderScaleZTime(v){
  noisescaleZ = v*10
}

export var time
export function beforeRender(delta) {
  time += delta/5000 * noisescaleZ
}

translate(-.5,-.5)

export var debugA, debugB, minA, minB, maxA, maxB
minA = .5
minB = .5
export function render2D(index, x, y) {
  x = x + 10
  y = y + 10
  h = noise(x*noisescaleX, y*noisescaleY, time)
  if (minA > h) minA = h
  if (maxA < h) maxA = h
  debugA = h
  hsv (h,1,1)
}

So I found a non-table using Simplex, and I’m shocked how MUCH slower it is… under 3fps on a v2.

I added some debug info, counting usage of the various parts, so the loop and 32k counts count how often a given function is called (so every 32000, we reset the loop count and add 1 to 32k count.)

looks like it’s mostly bit shifting happening. @wizard, any idea why this crawls? I figured removing table lookups would help, but this is so much slower.

2 Likes

@Scruffynerf did you ever get you V3 working and test it there? I’m fascinated by noise functions and am really enjoying watching your progress.

Not yet, last week was crazy and that v2 got crashed by Jeff’s music code. This week, I’ll hopefully repair both, with Ben’s help.

I might have lost my latest tweaked code on that v2, but nothing critical. Usually I try and back up to at least a text file.

More soon…

1 Like

Really sorry I didn’t test it on a v2 before uploading!

For what it’s worth, I also bricked a v2 after your report when I was troubleshooting, and also haven’t been able to recover it, even with the flashing tools.

I’ve been there - losing days of code to a PB I destroyed somehow. Sorry again Scruffy!

Sounds like the partition table or one of the two OTA partitions might be scrambled.

If you’re able to dump the flash back onto a computer, you may be able to extract the patterns from the SPIFFS partition with mkspiffs or spiffsimg.

2 Likes