rant about simplex noise, incompetence and stuff.

Discussion about everything. New games, 3d math, development tips...
Post Reply
Cube_
Posts: 1010
Joined: Mon Oct 24, 2011 10:03 pm
Location: 0x45 61 72 74 68 2c 20 69 6e 20 74 68 65 20 73 6f 6c 20 73 79 73 74 65 6d

rant about simplex noise, incompetence and stuff.

Post by Cube_ »

Code: Select all

21111111111111112211111111111111222... 111111
Image

That's the output of the simplex noise I'm using... what does this mean? How does it work? Don't ask me, I'm just as baffled. My scene generates with three block types visible (at least, since I use stone as the fallback material for non-grass/air blocks), the blocks I see in my scene are:
id 1: air
id 2: grass
id 3+: stone.
How is this when the simplex only generates values of 1 and 2? I take a plain integer as block id, that should reasonably cause it to only generate grass and air, given that this only contains id 1 and 2.
simplex is weird.

What happens if I clamp it to 1-32?

Code: Select all

1614121110910101091011111 ... 4
The chunk generated however does not have any similarity to the screenshot at all, reasonably one would expect more than an empty shell, no?
Image
this thing is hollow btw, in case you're wondering.

How am I printing these numbers?

Code: Select all

    std::ofstream f;
    f.open("Chunk_plaintext.txt", std::ios::out | std::ios::ate | std::ios::app);
are they floating point? sure, we could print them as float but my blocks have index values and I have no useful way to cast them to int that doesn't have data loss, this would be an issue with 2D simplex as well though, given that you have to put a roundoff point somewhere to get a sample at every pixel and a pixel can't have a floating point value,
what happens if we multiply the float by 100, divide that result by 2 and then cast to int?
I don't know, let's try.

Code: Select all

2 1.83933 1.73532 1.66185 1.60199 1.56883 1.59679 1.64369 1.59767 1.53981 1.5873 1.64683 1.67224 1.74292 1.79105 ... 1.76124 1.75255 1.84197 
This is the raw noise, added some space padding code, still don't see how this (when implicitly cast to int, which truncates it) would generate three different blocks when it would only truncate to 1's and 2's, that only covers 2 block id's.
I am confused, I don't understand why this black magic doesn't work as expected and I don't even know where to begin looking.
I'd wager I'm just incompetent, maybe I should just quit; given that I can't even get simplex noise to work right.
</rant>
All examples truncated for redundancy.

feel free to edit, move, delete, ridicule, or whatever.
"this is not the bottleneck you are looking for"
Foaly
Posts: 142
Joined: Tue Apr 15, 2014 8:45 am
Location: Germany

Re: rant about simplex noise, incompetence and stuff.

Post by Foaly »

I don't get the point of this.
Do you want to know something or is this some kind of article?
If this is a question I suppose I can't help you ay I've never heard of simplex noise before.
Cube_
Posts: 1010
Joined: Mon Oct 24, 2011 10:03 pm
Location: 0x45 61 72 74 68 2c 20 69 6e 20 74 68 65 20 73 6f 6c 20 73 79 73 74 65 6d

Re: rant about simplex noise, incompetence and stuff.

Post by Cube_ »

It's a rant/question thing, simplex noise is a noise type created by Ken Perlin to address the shortcomings of Perlin noise.
It's complicated math, I don't understand it, it doesn't cooperate and I don't find this entertaining at all, here's the relevant code if anyone wants to take a stab at how to get useable integers in the range of 0-n

Code: Select all

float scaled_octave_noise_3d( const float octaves, const float persistence, const float scale, const float loBound, const float hiBound, const float x, const float y, const float z ) {
    return octave_noise_3d(octaves, persistence, scale, x, y, z) * (hiBound - loBound) / 2 + (hiBound + loBound) / 2;
}
 
float octave_noise_3d( const float octaves, const float persistence, const float scale, const float x, const float y, const float z ) {
    float total = 0;
    float frequency = scale;
    float amplitude = 1;
 
    // We have to keep track of the largest possible amplitude,
    // because each octave adds more, and we need a value in [-1, 1].
    float maxAmplitude = 0;
 
    for( int i=0; i < octaves; i++ ) {
        total += raw_noise_3d( x * frequency, y * frequency, z * frequency ) * amplitude;
 
        frequency *= 2;
        maxAmplitude += amplitude;
        amplitude *= persistence;
    }
 
    return total / maxAmplitude;
}
 
float raw_noise_3d( const float x, const float y, const float z ) {
    float n0, n1, n2, n3; // Noise contributions from the four corners
 
    // Skew the input space to determine which simplex cell we're in
    float F3 = 1.0/3.0;
    float s = (x+y+z)*F3; // Very nice and simple skew factor for 3D
    int i = fastfloor(x+s);
    int j = fastfloor(y+s);
    int k = fastfloor(z+s);
 
    float G3 = 1.0/6.0; // Very nice and simple unskew factor, too
    float t = (i+j+k)*G3;
    float X0 = i-t; // Unskew the cell origin back to (x,y,z) space
    float Y0 = j-t;
    float Z0 = k-t;
    float x0 = x-X0; // The x,y,z distances from the cell origin
    float y0 = y-Y0;
    float z0 = z-Z0;
 
    // For the 3D case, the simplex shape is a slightly irregular tetrahedron.
    // Determine which simplex we are in.
    int i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
    int i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
 
    if(x0>=y0) {
        if(y0>=z0) { i1=1; j1=0; k1=0; i2=1; j2=1; k2=0; } // X Y Z order
        else if(x0>=z0) { i1=1; j1=0; k1=0; i2=1; j2=0; k2=1; } // X Z Y order
        else { i1=0; j1=0; k1=1; i2=1; j2=0; k2=1; } // Z X Y order
    }
    else { // x0<y0
        if(y0<z0) { i1=0; j1=0; k1=1; i2=0; j2=1; k2=1; } // Z Y X order
        else if(x0<z0) { i1=0; j1=1; k1=0; i2=0; j2=1; k2=1; } // Y Z X order
        else { i1=0; j1=1; k1=0; i2=1; j2=1; k2=0; } // Y X Z order
    }
 
    // A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z),
    // a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and
    // a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where
    // c = 1/6.
    float x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
    float y1 = y0 - j1 + G3;
    float z1 = z0 - k1 + G3;
    float x2 = x0 - i2 + 2.0*G3; // Offsets for third corner in (x,y,z) coords
    float y2 = y0 - j2 + 2.0*G3;
    float z2 = z0 - k2 + 2.0*G3;
    float x3 = x0 - 1.0 + 3.0*G3; // Offsets for last corner in (x,y,z) coords
    float y3 = y0 - 1.0 + 3.0*G3;
    float z3 = z0 - 1.0 + 3.0*G3;
 
    // Work out the hashed gradient indices of the four simplex corners
    int ii = i & 255;
    int jj = j & 255;
    int kk = k & 255;
    int gi0 = nPerm[ii+nPerm[jj+nPerm[kk]]] % 12;
    int gi1 = nPerm[ii+i1+nPerm[jj+j1+nPerm[kk+k1]]] % 12;
    int gi2 = nPerm[ii+i2+nPerm[jj+j2+nPerm[kk+k2]]] % 12;
    int gi3 = nPerm[ii+1+nPerm[jj+1+nPerm[kk+1]]] % 12;
 
    // Calculate the contribution from the four corners
    float t0 = 0.6 - x0*x0 - y0*y0 - z0*z0;
    if(t0<0) n0 = 0.0;
    else {
        t0 *= t0;
        n0 = t0 * t0 * dot(grad3[gi0], x0, y0, z0);
    }
 
    float t1 = 0.6 - x1*x1 - y1*y1 - z1*z1;
    if(t1<0) n1 = 0.0;
    else {
        t1 *= t1;
        n1 = t1 * t1 * dot(grad3[gi1], x1, y1, z1);
    }
 
    float t2 = 0.6 - x2*x2 - y2*y2 - z2*z2;
    if(t2<0) n2 = 0.0;
    else {
        t2 *= t2;
        n2 = t2 * t2 * dot(grad3[gi2], x2, y2, z2);
    }
 
    float t3 = 0.6 - x3*x3 - y3*y3 - z3*z3;
    if(t3<0) n3 = 0.0;
    else {
        t3 *= t3;
        n3 = t3 * t3 * dot(grad3[gi3], x3, y3, z3);
    }
 
    // Add contributions from each corner to get the final noise value.
    // The result is scaled to stay just inside [-1,1]
    return 32.0*(n0 + n1 + n2 + n3);
}
The functions have been sorted in order of when they're called, the scaled octave noise calls octave noise which calls raw noise (octave noise generates many octaves of raw noise, scaled octave noise then clamps it within values, to my understanding).

Code: Select all

float terrainGen::noiseGen3D( float loBound, float hiBound, int algo, float octaves, float persistence, int_least64_t x, int_least64_t y, int_least64_t z, float nScale)
{ 
    switch(algo)
    {
        case 0: return octave_noise_3d ( octaves, persistence, nScale, x, y, z ); 
                break; //3D octave simplex
        case 1: return scaled_octave_noise_3d ( octaves, persistence, nScale, loBound,
                                                hiBound, x, y, z );
                break; //3D scaled octave simplex 
        case 2: return scaled_raw_noise_3d ( loBound, hiBound, x, y, z );
                break; //3D scaled raw noise
        case 3: return raw_noise_3d( x, y, z ); 
                break; //3D raw noise
    }
    
}
 
//loBound, hiBound, algo (0-3), octaves, persistence, x, y, z, scale
/*
 * Specifically what they control is as follows:
 * loBound/hiBound: the range of values that can be generated, not sure how it works.
 * algo picks between the algorithms, octave, scaled octave, scaled raw, raw.
 * octaves: not sure exactly but I think it determines how many noise samles are averaged together
 * persistence: determines the smoothness of the curve. lower is smoother
 * x, y, z: coordinates, pls leave them as is desu.
 * scale: modulates the scale of the wavelength, lower value means lower frequency
 */
 
b_blocks[x][y][z].setBlock(static_cast<int>(tGen->noiseGen3D(1.0f, 3.0f, 1, 3.1415265935f, 0.55f, x, y, z, 0.03f)))
tGen is just an instance of terrainGen, the only relevant function is called (as terrain gen is just a container for the noise function to be used by the terrain related classes).

Essentially the issue here is that I don't understand how to get useable noise out of this that makes any bloody sense, the math is above me (as is most of mr. Perlin's work, you should see the reference implementation, many a programmer far greater than I'll ever be scratch their head on that one).

EDIT: On that matter I'd still need to figure out how to scale it on the y axis to get a lower density the higher we get (the idea being that the lower we get the lower the density gets, the lower the density gets the more likely air becomes, at 0,0,0 the density should be really low so that above you there's sky with only the occasional mountain peak sticking up and below you there's ground with only the occasional cave, ravine etc.
This math is going to be the end of me, I don't understand simplicies and everything about simplex noise is far above my head.

EDIT2: With further experimentation I just get more useless abstract results,
Image
That's a cube with three solid sides and that is lacking three sides, there are a few ways this chunk could theoretically render but all methods imply absolutely messed up noise that's nothing like terrain.

EDIT3:
Image
now that's a bit better, the range is still super weird though
and the range is still FUBAR, currently I'm downsampling the values with a division by 10, which just drops the values from 10-12 to 0-2
I want a range from 0-n (testing with n = 32 right now) but can't seem to find any case that generates these at all.

EDIT4:
Based on what I can tell the output is always going to be in a range like this, meaning I can interpolate the output and treat it as a density value, in which case... I'd still need to somehow scale it along the y axis to be able to generate useful terrain from this, however I'm not entirely sure where this scaling would logically occur, currently the noise is uniform, not particularly weighted towards any particular axis having a lower expected noise the higher that number goes.
"this is not the bottleneck you are looking for"
REDDemon
Developer
Posts: 1044
Joined: Tue Aug 31, 2010 8:06 pm
Location: Genova (Italy)

Re: rant about simplex noise, incompetence and stuff.

Post by REDDemon »

simplex noise just compute the "average" between 2 points and then add some random value. doing that recursively yield the same result as perlin.

If the noise don't have enough detail you probably need to scale it (if the function of noise returns values between 0 and 1, try to clamp it o 0.25 and 0.75 and then scale it to 0 1)

Code: Select all

 
float lowLimit= 0.25f;
float highLimit= 0.75f;
float sample = simplexNoise(....); //assume you already have a [0..1] value. if not just tell I'll review my code:P
sample =  sample<lowLimit? lowLimit: sample>highLimit? highLimit: sample;
sample = (sample-lowLimit) * (1.f/(highLimit-lowLimit)); //need double check here 
 
you can then remap values (sort of gamma correction) by using math functions that preserve the 0..1 range

Code: Select all

 
float exponent = 1.0f; // 0.5f is same of "squareroot", 2.0f is same of "square".
sample = std::pow(sample,exponent);
 
with those 3 values you can tweak the result.. and then you just need to map it to the 3 nubmers you need!

Code: Select all

 
sample *= 3.f;
int terrainType = 0;
if(sample<1)
    terrainType = 1;
else if (sample<2)
    terrainType = 2;
else
   terrainType = 3;
 
Junior Irrlicht Developer.
Real value in social networks is not about "increasing" number of followers, but about getting in touch with Amazing people.
- by Me
Cube_
Posts: 1010
Joined: Mon Oct 24, 2011 10:03 pm
Location: 0x45 61 72 74 68 2c 20 69 6e 20 74 68 65 20 73 6f 6c 20 73 79 73 74 65 6d

Re: rant about simplex noise, incompetence and stuff.

Post by Cube_ »

well yes, however you seem to have missed the point, the simplex implementation I use already does this, but the noise is uniform and uniform noise is terrible for generating terrain with, further I don't know where to scale it on the y axis so I can get terrain like curves out (I want it to scale from air at y positive to solid at y negative)

What I get now is essentially just a giant cloud of noise which isn't very useful
"this is not the bottleneck you are looking for"
REDDemon
Developer
Posts: 1044
Joined: Tue Aug 31, 2010 8:06 pm
Location: Genova (Italy)

Re: rant about simplex noise, incompetence and stuff.

Post by REDDemon »

Ah scaling the simplex along an axis:), I don't think your implementation is correct( provided you posted all the code).

I see few potential problems, the simplex noise is recursive, so means that it first computes A and B at random points, then it computes C=(A+B)/2, then it computes (A+C)/2 and (C+B)/2 .. (and of course it adds some random but decreasing variation each time, wich I don't see implemented in your code).

Now, that means that you need all intermediate values (you cannot just simply sample the function at arbitrary points) like you are doing, because in that case you may end up using uninitialized values resulting in wrong noise. (so if you try to get (A+C)/2 the algorithm, assuming it is correct, it would sample A and then C, but C has still to be initialized with (A+B)/2 because was not sampled before (A+C)/2.

Scaling the simplex along an axis is not that simple:

the simplest solution is just create a unitary cube with all sampled values (says a N * N *N cube: where N is one of the values: 2,3,5,9,17.. so powers of 2 + 1) and then scale the sampling space within it (so you may sample it as if it is a 20x40x20 cube, intermediate values are obtained via interpolation, and eventually some extra small random variation on that).
Junior Irrlicht Developer.
Real value in social networks is not about "increasing" number of followers, but about getting in touch with Amazing people.
- by Me
Cube_
Posts: 1010
Joined: Mon Oct 24, 2011 10:03 pm
Location: 0x45 61 72 74 68 2c 20 69 6e 20 74 68 65 20 73 6f 6c 20 73 79 73 74 65 6d

Re: rant about simplex noise, incompetence and stuff.

Post by Cube_ »

afaik the implementation is correct, the multiple octaves are done by the octave noise function that generates multiple raw noises and samples them together, the issue of course is that the math in the raw noise (which I blatantly lifted from a port of the reference implementation in java) is way above what I can decipher, I know where y is used but I don't particularly understand how it's used and as such I have a hard time getting it to have a lower likelihood of a high value at high z, ideally I'd want a likelihood of 0 really high up with a gradually increasing likelihood of a solid block as one moves down (which would allow mountain peaks and whatnot)
If I can't figure this out I'll proably have to go and study some math and then write my own implementation, although I'd rather not.

Procedural generation is one of those things that sound easy as a concept, look easy on paper and end up being deceptively hard in practice.
"this is not the bottleneck you are looking for"
REDDemon
Developer
Posts: 1044
Joined: Tue Aug 31, 2010 8:06 pm
Location: Genova (Italy)

Re: rant about simplex noise, incompetence and stuff.

Post by REDDemon »

considered sampling 3d sin functions ? much simpler and effective, and you can scale along the direction you like most.
something like

Code: Select all

 
x = sin(3.*z)+2.*sin(1.333*x)+y*sin(x);
 
Junior Irrlicht Developer.
Real value in social networks is not about "increasing" number of followers, but about getting in touch with Amazing people.
- by Me
Cube_
Posts: 1010
Joined: Mon Oct 24, 2011 10:03 pm
Location: 0x45 61 72 74 68 2c 20 69 6e 20 74 68 65 20 73 6f 6c 20 73 79 73 74 65 6d

Re: rant about simplex noise, incompetence and stuff.

Post by Cube_ »

would that generate terrain like values with a large amount of unique chunk configurations?
in worst case I'll probably just have to resort to using a library for it, meaning I have to review yet another software license *sigh*
"this is not the bottleneck you are looking for"
REDDemon
Developer
Posts: 1044
Joined: Tue Aug 31, 2010 8:06 pm
Location: Genova (Italy)

Re: rant about simplex noise, incompetence and stuff.

Post by REDDemon »

It will generate unique configurations only if least common multiple of sinus arguments is small. if you use numbers like "sin(sqrt(2)*x)" or "sin(pi*y)" it will not generate unique configurations (irrational numbers are good for that), especially when you start using 3/4 sinus waves for each coordinate it will look very noisy :).

Of course it is computational intensive using much sinus waves, but it is extremily simple to write.
Junior Irrlicht Developer.
Real value in social networks is not about "increasing" number of followers, but about getting in touch with Amazing people.
- by Me
Granyte
Posts: 850
Joined: Tue Jan 25, 2011 11:07 pm
Contact:

Re: rant about simplex noise, incompetence and stuff.

Post by Granyte »

Your simplex code seem fine but the devil is in the detail with these. Ill have more time to look at it next week

Are you feeding your noise function with coordinates ?
if so do you scale them ?
In my usage i noted it was better to fill your equivalent of a whole chunk using values from 0 to 1 then sampling discrete values because the frequency of the noise is so high that we get non sensical result if we go to fast.

exemple the whole planet you see there has been sampeled from a 4d perlin noise fucntion (and other noises added) the coordinate values used were between -128 to 128 yet still see how dense the mountain ranges are
[spoiler]
Image
[/spoiler]
Cube_
Posts: 1010
Joined: Mon Oct 24, 2011 10:03 pm
Location: 0x45 61 72 74 68 2c 20 69 6e 20 74 68 65 20 73 6f 6c 20 73 79 73 74 65 6d

Re: rant about simplex noise, incompetence and stuff.

Post by Cube_ »

mmm, I currently feed the simplex raw integral coordinates (arbitrary precision, not fixed system types, happens with system types too though), I don't scale them in any way at all, the chunk is filled with just 0's to begin with.
"this is not the bottleneck you are looking for"
Granyte
Posts: 850
Joined: Tue Jan 25, 2011 11:07 pm
Contact:

Re: rant about simplex noise, incompetence and stuff.

Post by Granyte »

Yes that's what i'm talking about the noise periode is to short (At least it was in my experimentation) to be sampled in integral you would need to sample it in float and divide the coordinate by something like 128 befor feeding it to your algorythm
Cube_
Posts: 1010
Joined: Mon Oct 24, 2011 10:03 pm
Location: 0x45 61 72 74 68 2c 20 69 6e 20 74 68 65 20 73 6f 6c 20 73 79 73 74 65 6d

Re: rant about simplex noise, incompetence and stuff.

Post by Cube_ »

okay... so, do a static_cast from int to float, divide by a divisor to return a floating point and then churn out noise from that, returning integral values, yes?
sounds simple enough, I'll make an attempt at implementing that, it's simple enough really.
"this is not the bottleneck you are looking for"
Cube_
Posts: 1010
Joined: Mon Oct 24, 2011 10:03 pm
Location: 0x45 61 72 74 68 2c 20 69 6e 20 74 68 65 20 73 6f 6c 20 73 79 73 74 65 6d

Re: rant about simplex noise, incompetence and stuff.

Post by Cube_ »

okay, so while I'm now getting bizarre results I can at least use the values
Image

Code: Select all

0 -142 0 85 0 100 0 -54 0 129 0 -19 0 -9 0 -139 103 59 11 -73 -96 -12 -62 -59 -41 1 -25 34 -11 -84 114 13 0 //REDACTED 
Essentially what I do is cast the coordinates to floating point, divide them by .000000256f (because 128 returned just hiBound repeated over and over, if highbound gets really high @65535 it repeated 32768 over and over again, not a very useful value, with a divisor between 0.0000256 and 0.000000256 I get a lot of varied results, I probably want to change the division factor on the y axis to scale the density along y... maybe, I'm not entirely sure.

the current lo/hiBound is at neg/pos -255/256, which would allow 512 density values, this should be easily upscalable.
now I need to treat the noise output as a density and build terrain from densities, probably pulled from a dynamically built block id list sorted by density.

currently though anything less than 0 is treated as... invalid? I don't know how an enumerator initializes a value lower than the lowest bound (manually specified to be 0, I'd assume it rounds up)
this should likely be changeable to scale from 0 to 512 with identical distribution.
Anywho, as is this is more or less useableish, except I'm not entirely sure what would happen as coordinates grow, currently this would still generate uniform noise in all directions and I don't exactly know how I would tame that, but whatever this'll do for now; I can't dwell on this for ever.
"this is not the bottleneck you are looking for"
Post Reply