Initial commit

master
itycodes 2 months ago
commit 939bf95a81

@ -0,0 +1,224 @@
namespace Noise;
using System;
// Used for the hashing example that's commented out
//using System.Text;
//using System.Security.Cryptography;
// Based on https://adrianb.io/2014/08/09/perlinnoise.html
public class PerlinNoise {
// Hash lookup table as defined by Ken Perlin. This is a randomly
// arranged array of all numbers from 0-255 inclusive.
// Works as the seed.
private static readonly int[] permutation = { 151,160,137,91,90,15,
131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23,
190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,
88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166,
77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244,
102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196,
135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123,
5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,
223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9,
129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228,
251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107,
49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254,
138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180
};
// Later on, in the hash function, access similar to p[p[_]+255+1] is performed
// p contains two copies of permutation, len 512 in total.
// The max number p[n] can return is 255, and 255+255+1 is 511,
// which is the largest index in the 512 long array.
private static readonly int[] p;
// Controls intentional noise repeating
// Do note the noise will repeat each 256 anyway, due to the hashing function
// (to be exact, the preprocessing of the input for the hashing function)
private static readonly int repeat = 0;
static PerlinNoise() {
p = new int[512];
for(int i=0;i<512;i++) {
p[i] = permutation[i%256];
}
}
// TODO figure out how the vectors were chosen
// TODO figure out whether there is bias due to a few vectors being repeated
// TODO experiment with other vectors
//
// Choose a random vector from
//
// The 12 vectors
// (1,1,0),(-1,1,0),(1,-1,0),(-1,-1,0),
// (1,0,1),(-1,0,1),(1,0,-1),(-1,0,-1),
// (0,1,1),(0,-1,1),(0,1,-1),(0,-1,-1)
//
// Then calculate the dot product.
// Take as an example the first one, (1,1,0)
// The dot product would thus be 1x + 1y + 0z. Thus, x + y
//
// The equations below are just simplified dot products.
//
// We have 12 vectors but 16 cases, so some of the vectors repeat.
//
// The vectors represent the middles of edges of a cube.
// A cube has 12 edges, so 12 vectors.
public static double grad(int hash, double x, double y, double z) {
switch(hash & 0xF)
{
// Unique
case 0x0: return x + y; // 1, 1, 0 A
case 0x1: return -x + y; // -1, 1, 0 B
case 0x2: return -y + z; // 0, -1, 1 C
case 0x3: return -x - y; // -1, -1, 0
case 0x4: return x + z; // 1, 0, 1
case 0x5: return -x + z; // -1, 0, 1
case 0x6: return x - z; // 1, 0, -1
case 0x7: return -x - z; // -1, 0, -1
case 0x8: return y + z; // 0, 1, 1
case 0x9: return x - y; // 1, -1, 0
case 0xA: return y - z; // 0, 1, -1
case 0xB: return -y - z; // 0, -1, -1
// Repetitions
case 0xC: return y + x; // 1, 1, 0 A
case 0xD: return -y + z; // 0, -1, 1 C
case 0xE: return y - x; // -1, 1, 0 B
case 0xF: return -y - z; // 0, -1, 1 C
default: return 0; // never happens
}
}
// TODO Unsure of other properties it needs to satisfy
// TODO Unsure why it's required, but without it,
// edges between cells/cubes become visible
// TODO There are indeed other properties.
// 1 / (1 + Math.Exp(-12 * (x - 0.5)))
// satisfies the 3 defined ones, but does not give good results.
//
// A magical easing function
//
// Intended range is 0..1 -> 0..1
// It's a simple sigmoid that satisfies
// f(0) = 0
// f(1) = 1
//
// Fade function as defined by Ken Perlin. This eases coordinate values
// so that they will ease towards integral values. This ends up smoothing
// the final output.
public static double fade(double t) {
// 6t^5 - 15t^4 + 10t^3
return t * t * t * (t * (t * 6 - 15) + 10);
}
// Incrementing function that wraps around based on `repeat`
public static int inc(int num) {
num++;
if (repeat > 0) num %= repeat;
return num;
}
// Linear Interpolate
public static double lerp(double a, double b, double x) {
return a + x * (b - a);
}
// Hash function
// Any hash function can be utilized (for example sha256 works), though this one is fast and simple.
// This hash function performs 3 random "jumps" in the permutations array,
// seeded & offset by the coordinates and offset by the number it landed on.
private static int hash(int xi, int yi, int zi) {
return p[p[p[xi]+yi]+zi];
}
// Here is a Sha256 replacement, to show that the permutations are literally just a prng with nothing special
//private static int hash(int xi, int yi, int zi) {
// string inputString = $"{xi},{yi},{zi}";
// byte[] hashBytes = SHA256.HashData(Encoding.UTF8.GetBytes(inputString));
// return BitConverter.ToInt32(hashBytes, 0);
//}
// TODO figure out why n.0 always returns 0.5, and n.5 always returns numbers like
// 0.75, 0.5 or 0.375. Overall, the noise seems to break down when it is not "zoomed in" enough.
public static double perlin(double x, double y, double z) {
// TODO this is probably useless
// If we have any repeat on, change the coordinates to their "local" repetitions
if(repeat > 0) {
x = x%repeat;
y = y%repeat;
z = z%repeat;
}
// Calculate the "unit cube" that the point asked will be located in
// The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
// plus 1. Next we calculate the location (from 0.0 to 1.0) in that cube.
// This is just a singular corner of the cube.
// Modulos with 256 - this causes integers to repeat every 256.
// Thus, 0.25 = 256.25, 3.6 = 259.6...
// And yes, this causes a visual repeating pattern.
// The edge is not visible under normal conditions, though.
int xi = (int)x & 255;
int yi = (int)y & 255;
int zi = (int)z & 255;
// 0..1 coordinates within the cube
double xf = x-(int)x;
double yf = y-(int)y;
double zf = z-(int)z;
// TODO figure out why the lerping coordinates are eased rather than anything else.
// Coordinates used for the lerping
double u = fade(xf);
double v = fade(yf);
double w = fade(zf);
// Magic hash
// Compute 8 hashes for each vertex of the cube, from each coordinate
int aaa, aba, aab, abb, baa, bba, bab, bbb;
aaa = hash( xi, yi, zi);
aba = hash( xi, inc(yi), zi);
aab = hash( xi, yi, inc(zi));
abb = hash( xi, inc(yi), inc(zi));
baa = hash(inc(xi), yi, zi);
bba = hash(inc(xi), inc(yi), zi);
bab = hash(inc(xi), yi, inc(zi));
bbb = hash(inc(xi), inc(yi), inc(zi));
// TODO figure out how the -1s work
// TODO the u, v, and w being used in this order matters. Why?
// TODO figure out why lerping, and this particular way works
//
// This is then used as the seed for the grad() function that uses it to
// pick a random edge vector and compute the dot product between it and the input vector,
// offset into 8 corners, where one is the origin corner of the cube
// (This works fine because the origin corner of the cube is also the origin of the vectors)
// Results in vectors pointing from the corners to the input point specified by the input vector.
//
// The gradient function calculates the dot product between a pseudorandom
// gradient vector and the vector from the input coordinate to the 8
// surrounding points in its unit cube.
// This is all then lerped together as a sort of weighted average based on the faded (u,v,w)
// values we made earlier.
double x1, x2, x3, x4, y1, y2;
x1 = lerp(grad(aaa, xf , yf , zf ),
grad(baa, xf-1, yf , zf ), u);
x2 = lerp(grad(aba, xf , yf-1, zf ),
grad(bba, xf-1, yf-1, zf ), u);
y1 = lerp(x1, x2, v);
x3 = lerp(grad(aab, xf , yf , zf-1),
grad(bab, xf-1, yf , zf-1), u);
x4 = lerp(grad(abb, xf , yf-1, zf-1),
grad(bbb, xf-1, yf-1, zf-1), u);
y2 = lerp(x3, x4, v);
// For convenience we bind the result to 0 - 1 (theoretical min/max before is [-1, 1])
double res = (lerp (y1, y2, w)+1)/2;
return res;
}
}

@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

@ -0,0 +1,42 @@
using Noise;
using System.IO;
long width = 1024;
long height = 1024;
double scaleX = 128.0;
double scaleY = 128.0;
byte[] resBytes = new byte[width*height*3];
// Console.WriteLine(PerlinNoise.perlin(3.25 , 0, 0));
// Console.WriteLine(PerlinNoise.perlin(3.25+256, 0, 0));
// Console.WriteLine(PerlinNoise.perlin(3.25+512, 0, 0));
for(int i = 0; i<width; i++) {
for(int j = 0; j<height; j++) {
double res = PerlinNoise.perlin(((double)i)/scaleX, ((double)j)/scaleY, 1.0);
byte bRes = (byte)(res * 256);
resBytes[(i*width+j)*3+0] = bRes;
resBytes[(i*width+j)*3+1] = bRes;
resBytes[(i*width+j)*3+2] = bRes;
}
}
using (var fs = new FileStream("out.rif", FileMode.Create, FileAccess.Write)) {
// Convert width to a byte array in big-endian order
byte[] widthBytes = BitConverter.GetBytes(width);
if (BitConverter.IsLittleEndian)
Array.Reverse(widthBytes); // Convert to big-endian if the system is little-endian
byte[] header = new byte[] {
// Magic
0x72, 0x69, 0x66, 0x56, 0x30, 0x30, 0x30, 0x31,
// Width
widthBytes[0], widthBytes[1], widthBytes[2], widthBytes[3],
widthBytes[4], widthBytes[5], widthBytes[6], widthBytes[7],
// Format (R8G8B8)
0x00
};
fs.Write(header, 0, header.Length);
fs.Write(resBytes, 0, resBytes.Length);
}
Loading…
Cancel
Save