Skip to content

Instantly share code, notes, and snippets.

@howmanysmall
Last active May 27, 2022 00:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save howmanysmall/8fc9de328e30b75b74937b4c0f5f4d2e to your computer and use it in GitHub Desktop.
Save howmanysmall/8fc9de328e30b75b74937b4c0f5f4d2e to your computer and use it in GitHub Desktop.
const GRAD_3 = [
[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],
];
const GRAD_4 = [
[0, 1, 1, 1],
[0, 1, 1, -1],
[0, 1, -1, 1],
[0, 1, -1, -1],
[0, -1, 1, 1],
[0, -1, 1, -1],
[0, -1, -1, 1],
[0, -1, -1, -1],
[1, 0, 1, 1],
[1, 0, 1, -1],
[1, 0, -1, 1],
[1, 0, -1, -1],
[-1, 0, 1, 1],
[-1, 0, 1, -1],
[-1, 0, -1, 1],
[-1, 0, -1, -1],
[1, 1, 0, 1],
[1, 1, 0, -1],
[1, -1, 0, 1],
[1, -1, 0, -1],
[-1, 1, 0, 1],
[-1, 1, 0, -1],
[-1, -1, 0, 1],
[-1, -1, 0, -1],
[1, 1, 1, 0],
[1, 1, -1, 0],
[1, -1, 1, 0],
[1, -1, -1, 0],
[-1, 1, 1, 0],
[-1, 1, -1, 0],
[-1, -1, 1, 0],
[-1, -1, -1, 0],
];
const F2 = (math.sqrt(3) - 1) / 2;
const G2 = (3 - math.sqrt(3)) / 6;
const F3 = 1 / 3;
const G3 = 1 / 6;
const F4 = (math.sqrt(5) - 1) / 4;
const G4 = (5 - math.sqrt(5)) / 20;
const newPerm = () => {
const perm = new Array<number>(256);
for (let index = 0; index < 256; index += 1) perm[index] = index;
return perm;
};
export default class Simplex {
private perm: Array<number>;
private permMod12: Array<number>;
/**
* Returns a Simplex generator with a state initialized by a random shuffle.
* @param {Array<number>?} perm An array to set as the `perm` entry in the class.
* @param {Array<number>?} permMod12 An array to set as the `permMod12` entry in the class.
*/
public constructor(perm?: Array<number>, permMod12?: Array<number>) {
const randomLib = new Random((os.clock() % 1) * 1e7);
const permutations = newPerm();
for (let index = permutations.size(); index > 1; index -= 1) {
const jndex = randomLib.NextInteger(1, index);
const swap = permutations[index];
permutations[index] = permutations[jndex];
permutations[jndex] = swap;
}
const isPermUndefined = perm === undefined;
const isPermMod12Undefined = permMod12 === undefined;
if (isPermUndefined) {
if (isPermMod12Undefined) {
this.perm = new Array<number>(512);
this.permMod12 = new Array<number>(512);
for (let index = 0; index < 512; index += 1) {
this.perm[index] = permutations[index % 256];
this.permMod12[index] = this.perm[index] % 12;
}
} else {
this.perm = new Array<number>(512);
this.permMod12 = permMod12;
for (let index = 0; index < 512; index += 1) this.perm[index] = permutations[index % 256];
}
} else {
if (isPermMod12Undefined) {
this.perm = perm;
this.permMod12 = new Array<number>(512);
for (let index = 0; index < 512; index += 1) this.permMod12[index] = this.perm[index] % 12;
} else {
this.perm = perm;
this.permMod12 = permMod12;
}
}
}
/**
* Returns a Simplex generator with a state initialized by a table of permutations.
* @param {Array<number>} permutations An array containing some permutation of each integer between 0 and 255, inclusive.
* @returns {Simplex}
*/
public static FromArray(permutations: Array<number>): Simplex {
const found = new Set<number>();
for (let index = 0; index < 256; index += 1) {
const value = permutations[index];
if (value === undefined) error(`permutations missing index ${index}`, 2);
found.add(value);
}
for (let index = 0; index < 256; index += 1)
if (!found.has(index)) error(`permutations missing value ${index}`, 2);
const perm = new Array<number>(512);
const permMod12 = new Array<number>(512);
for (let index = 0; index < 512; index += 1) {
perm[index] = permutations[index % 256];
permMod12[index] = perm[index] % 12;
}
return new Simplex(perm, permMod12);
}
/**
* Returns a Simplex generator with a state initialized by a random function.
* @param {(index: number) => number} generator A function that receives an integer, and returns an integer between 1 and the given value, inclusive. A generated array of permutations will be shuffled using this function. `math.random` is an example of such a function.
* @returns {Simplex}
*/
public static FromFunction(generator: (index: number) => number): Simplex {
const permutations = newPerm();
for (let index = permutations.size(); index > 1; index -= 1) {
const jndex = generator(index);
const swap = permutations[index];
permutations[index] = permutations[jndex];
permutations[jndex] = swap;
}
const perm = new Array<number>(512);
const permMod12 = new Array<number>(512);
for (let index = 0; index < 512; index += 1) {
perm[index] = permutations[index % 256];
permMod12[index] = perm[index] % 12;
}
return new Simplex(perm, permMod12);
}
/**
* Returns a Simplex generator with a state initialized by a seed for a random number generator.
* @param {number} seed A number, which is used as a random seed to shuffle a generated array of permutations.
* @returns {Simplex}
*/
public static FromSeed(seed: number): Simplex {
const randomLib = new Random(seed);
const permutations = newPerm();
for (let index = permutations.size(); index > 1; index -= 1) {
const jndex = randomLib.NextInteger(1, index);
const swap = permutations[index];
permutations[index] = permutations[jndex];
permutations[jndex] = swap;
}
const perm = new Array<number>(512);
const permMod12 = new Array<number>(512);
for (let index = 0; index < 512; index += 1) {
perm[index] = permutations[index % 256];
permMod12[index] = perm[index] % 12;
}
return new Simplex(perm, permMod12);
}
/**
* Returns a Simplex generator with a state initialized by a Random source.
* @param randomLib A Random value, which is used to shuffle a generated array of permutations.
* @returns {Simplex}
*/
public static FromRandom(randomLib: Random): Simplex {
const permutations = newPerm();
for (let index = permutations.size(); index > 1; index -= 1) {
const jndex = randomLib.NextInteger(1, index) - 1;
const swap = permutations[index];
permutations[index] = permutations[jndex];
permutations[jndex] = swap;
}
const perm = new Array<number>(512);
const permMod12 = new Array<number>(512);
for (let index = 0; index < 512; index += 1) {
perm[index] = permutations[index % 256];
permMod12[index] = perm[index] % 12;
}
return new Simplex(perm, permMod12);
}
/**
* Returns a number in the interval [-1, 1] based on the given two-dimensional coordinates and the generator's permutation state.
* @param {number} x The X value to generate with.
* @param {number} y The Y value to generate with.
* @returns {number} The 2D noise value.
*/
public Noise2d(x: number, y: number): number {
const perm = this.perm;
const permMod12 = this.permMod12;
let n0, n1, n2; // Noise contributions from the three corners
// Skew the input space to determine which simplex cell we're in
const s = (x + y) * F2; // Hairy factor for 2D
const i = math.floor(x + s);
const j = math.floor(y + s);
const t = (i + j) * G2;
let x0 = i - t; // Unskew the cell origin back to (x,y) space
let y0 = j - t;
x0 = x - x0; // The x,y distances from the cell origin
y0 = y - y0;
// For the 2D case, the simplex shape is an equilateral triangle.
// Determine which simplex we are in.
let i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
if (x0 > y0) {
i1 = 1;
j1 = 0; // lower triangle, xy order: (0,0)->(1,0)->(1,1)
} else {
i1 = 0;
j1 = 1; // upper triangle, yx order: (0,0)->(0,1)->(1,1)
}
// A step of (1,0) in (i,j) means a step of (1-c, -c) in (x,y), and
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
// c = (3-sqrt(3))/6
const x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
const y1 = y0 - j1 + G2;
const x2 = x0 - 1 + 2 * G2; // Offsets for last corner in (x,y) unskewed coords
const y2 = y0 - 1 + 2 * G2;
// Work out the hashed gradient indices of the three simplex corners
const ii = i % 256;
const jj = j % 256;
// Calculate the contribution from the three corners
let t0 = 0.5 - x0 * x0 - y0 * y0;
if (t0 < 0) n0 = 0;
else {
t0 *= t0;
const g = GRAD_3[permMod12[ii + perm[jj]]];
n0 = t0 * t0 * (g[0] * x0 + g[1] * y0); // (x,y) of GRAD_3 used for 2D gradient
}
let t1 = 0.5 - x1 * x1 - y1 * y1;
if (t1 < 0) n1 = 0;
else {
t1 *= t1;
const g = GRAD_3[permMod12[ii + i1 + perm[jj + j1]]];
n1 = t1 * t1 * (g[0] * x1 + g[1] * y1);
}
let t2 = 0.5 - x2 * x2 - y2 * y2;
if (t2 < 0) n2 = 0;
else {
t2 *= t2;
const g = GRAD_3[permMod12[ii + 1 + perm[1 + jj]]];
n2 = t2 * t2 * (g[0] * x2 + g[1] * y2);
}
// Add contributions from each corner to get the final noise value.
// The result is scaled to return values in the interval [-1,1].
return 70 * (n0 + n1 + n2);
}
/**
* Returns a number in the interval [-1, 1] based on the given three-dimensional coordinates and the generator's permutation state.
* @param {number} x The X value to generate with.
* @param {number} y The Y value to generate with.
* @param {number} z The Z value to generate with.
* @returns {number} The 3D noise value.
*/
public Noise3d(x: number, y: number, z: number): number {
const perm = this.perm;
const permMod12 = this.permMod12;
let n0, n1, n2, n3; // Noise contributions from the four corners
// Skew the input space to determine which simplex cell we're in
const s = (x + y + z) * F3; // Very nice and simple skew factor for 3D
const i = math.floor(x + s);
const j = math.floor(y + s);
const k = math.floor(z + s);
const t = (i + j + k) * G3;
const X0 = i - t; // Unskew the cell origin back to (x,y,z) space
const Y0 = j - t;
const Z0 = k - t;
const x0 = x - X0; // The x,y,z distances from the cell origin
const y0 = y - Y0;
const z0 = z - Z0;
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
// Determine which simplex we are in.
let i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
let i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
if (x0 >= y0) {
if (y0 >= z0) {
// X Y Z order
i1 = 1;
j1 = 0;
k1 = 0;
i2 = 1;
j2 = 1;
k2 = 0;
} else if (x0 >= z0) {
// X Z Y order
i1 = 1;
j1 = 0;
k1 = 0;
i2 = 1;
j2 = 0;
k2 = 1;
} else {
// Z X Y order
i1 = 0;
j1 = 0;
k1 = 1;
i2 = 1;
j2 = 0;
k2 = 1;
}
} else {
// x0 < y0
if (y0 < z0) {
// Z Y X order
i1 = 0;
j1 = 0;
k1 = 1;
i2 = 0;
j2 = 1;
k2 = 1;
} else if (x0 < z0) {
// Y Z X order
i1 = 0;
j1 = 1;
k1 = 0;
i2 = 0;
j2 = 1;
k2 = 1;
} else {
// Y X Z order
i1 = 0;
j1 = 1;
k1 = 0;
i2 = 1;
j2 = 1;
k2 = 0;
}
}
// 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.
const x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
const y1 = y0 - j1 + G3;
const z1 = z0 - k1 + G3;
const x2 = x0 - i2 + 2 * G3; // Offsets for third corner in (x,y,z) coords
const y2 = y0 - j2 + 2 * G3;
const z2 = z0 - k2 + 2 * G3;
const x3 = x0 - 1 + 3 * G3; // Offsets for last corner in (x,y,z) coords
const y3 = y0 - 1 + 3 * G3;
const z3 = z0 - 1 + 3 * G3;
// Work out the hashed gradient indices of the four simplex corners
const ii = i % 256;
const jj = j % 256;
const kk = k % 256;
// Calculate the contribution from the four corners
let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
if (t0 < 0) n0 = 0;
else {
t0 *= t0;
const g = GRAD_3[permMod12[ii + perm[jj + perm[kk]]]];
n0 = t0 * t0 * (g[0] * x0 + g[1] * y0 + g[2] * z0);
}
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
if (t1 < 0) n1 = 0;
else {
t1 *= t1;
const g = GRAD_3[permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]]];
n1 = t1 * t1 * (g[0] * x1 + g[1] * y1 + g[2] * z1);
}
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
if (t2 < 0) n2 = 0;
else {
t2 *= t2;
const g = GRAD_3[permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]]];
n2 = t2 * t2 * (g[0] * x2 + g[1] * y2 + g[2] * z2);
}
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
if (t3 < 0) n3 = 0;
else {
t3 *= t3;
const g = GRAD_3[permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]]];
n3 = t3 * t3 * (g[0] * x3 + g[1] * y3 + g[2] * z3);
}
// Add contributions from each corner to get the final noise value.
// The result is scaled to stay just inside [-1,1]
return 32 * (n0 + n1 + n2 + n3);
}
/**
* Returns a number in the interval [-1, 1] based on the given four-dimensional coordinates and the generator's permutation state.
* @param {number} x The X to generate with.
* @param {number} y The Y to generate with.
* @param {number} z The Z to generate with.
* @param {number} w The W to generate with.
* @returns {number} The 4D noise value.
*/
public Noise4d(x: number, y: number, z: number, w: number): number {
const perm = this.perm;
let n0, n1, n2, n3, n4; // Noise contributions from the five corners
// Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
const s = (x + y + z + w) * F4; // Factor for 4D skewing
const i = math.floor(x + s);
const j = math.floor(y + s);
const k = math.floor(z + s);
const l = math.floor(w + s);
const t = (i + j + k + l) * G4; // Factor for 4D unskewing
const X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space
const Y0 = j - t;
const Z0 = k - t;
const W0 = l - t;
const x0 = x - X0; // The x,y,z,w distances from the cell origin
const y0 = y - Y0;
const z0 = z - Z0;
const w0 = w - W0;
// For the 4D case, the simplex is a 4D shape I won't even try to describe.
// To find out which of the 24 possible simplices we're in, we need to
// determine the magnitude ordering of x0, y0, z0 and w0.
// Six pair-wise comparisons are performed between each possible pair
// of the four coordinates, and the results are used to rank the numbers.
let rankx = 0;
let ranky = 0;
let rankz = 0;
let rankw = 0;
if (x0 > y0) rankx += 1;
else ranky += 1;
if (x0 > z0) rankx += 1;
else rankz += 1;
if (x0 > w0) rankx += 1;
else rankw += 1;
if (y0 > z0) ranky += 1;
else rankz += 1;
if (y0 > w0) ranky += 1;
else rankw += 1;
if (z0 > w0) rankz += 1;
else rankw += 1;
// simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
// Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
// impossible. Only the 24 indices which have non-zero entries make any sense.
// We use a thresholding to set the coordinates in turn from the largest magnitude.
// The integer offsets for the second simplex corner
// Rank 3 denotes the largest coordinate.
const i1 = rankx >= 3 ? 1 : 0;
const j1 = ranky >= 3 ? 1 : 0;
const k1 = rankz >= 3 ? 1 : 0;
const l1 = rankw >= 3 ? 1 : 0;
// The integer offsets for the third simplex corner
// Rank 2 denotes the second largest coordinate.
const i2 = rankx >= 2 ? 1 : 0;
const j2 = ranky >= 2 ? 1 : 0;
const k2 = rankz >= 2 ? 1 : 0;
const l2 = rankw >= 2 ? 1 : 0;
// The integer offsets for the fourth simplex corner
// Rank 1 denotes the second smallest coordinate.
const i3 = rankx >= 1 ? 1 : 0;
const j3 = ranky >= 1 ? 1 : 0;
const k3 = rankz >= 1 ? 1 : 0;
const l3 = rankw >= 1 ? 1 : 0;
// The fifth corner has all coordinate offsets = 1, so no need to compute that.
const x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords
const y1 = y0 - j1 + G4;
const z1 = z0 - k1 + G4;
const w1 = w0 - l1 + G4;
const x2 = x0 - i2 + 2 * G4; // Offsets for third corner in (x,y,z,w) coords
const y2 = y0 - j2 + 2 * G4;
const z2 = z0 - k2 + 2 * G4;
const w2 = w0 - l2 + 2 * G4;
const x3 = x0 - i3 + 3 * G4; // Offsets for fourth corner in (x,y,z,w) coords
const y3 = y0 - j3 + 3 * G4;
const z3 = z0 - k3 + 3 * G4;
const w3 = w0 - l3 + 3 * G4;
const x4 = x0 - 1 + 4 * G4; // Offsets for last corner in (x,y,z,w) coords
const y4 = y0 - 1 + 4 * G4;
const z4 = z0 - 1 + 4 * G4;
const w4 = w0 - 1 + 4 * G4;
// Work out the hashed gradient indices of the five simplex corners
const ii = i % 256;
const jj = j % 256;
const kk = k % 256;
const ll = l % 256;
// Calculate the contribution from the five corners
let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;
if (t0 < 0) n0 = 0;
else {
t0 *= t0;
const g = GRAD_4[perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32];
n0 = t0 * t0 * (g[0] * x0 + g[1] * y0 + g[2] * z0 + g[3] * w0);
}
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;
if (t1 < 0) n1 = 0;
else {
t1 *= t1;
const g = GRAD_4[perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32];
n1 = t1 * t1 * (g[0] * x1 + g[1] * y1 + g[2] * z1 + g[3] * w1);
}
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;
if (t2 < 0) n2 = 0;
else {
t2 *= t2;
const g = GRAD_4[perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32];
n2 = t2 * t2 * (g[0] * x2 + g[1] * y2 + g[2] * z2 + g[3] * w2);
}
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;
if (t3 < 0) n3 = 0;
else {
t3 *= t3;
const g = GRAD_4[perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32];
n3 = t3 * t3 * (g[0] * x3 + g[1] * y3 + g[2] * z3 + g[3] * w3);
}
let t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;
if (t4 < 0) n4 = 0;
else {
t4 *= t4;
const g = GRAD_4[perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32];
n4 = t4 * t4 * (g[0] * x4 + g[1] * y4 + g[2] * z4 + g[3] * w4);
}
// Sum up and scale the result to cover the range [-1,1]
return 27 * (n0 + n1 + n2 + n3 + n4);
}
}
const simplexMetatable = Simplex as LuaMetatable<Simplex>;
simplexMetatable.__call = (simplex, x, y, z, w) => {
if (w !== undefined) return simplex.Noise4d(x as number, y as number, z as number, w as number);
else if (z !== undefined) return simplex.Noise3d(x as number, y as number, z as number);
else return simplex.Noise2d(x as number, y as number);
};
simplexMetatable.__tostring = () => "Simplex";
const GRAD_3 = [
[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],
];
const GRAD_4 = [
[0, 1, 1, 1],
[0, 1, 1, -1],
[0, 1, -1, 1],
[0, 1, -1, -1],
[0, -1, 1, 1],
[0, -1, 1, -1],
[0, -1, -1, 1],
[0, -1, -1, -1],
[1, 0, 1, 1],
[1, 0, 1, -1],
[1, 0, -1, 1],
[1, 0, -1, -1],
[-1, 0, 1, 1],
[-1, 0, 1, -1],
[-1, 0, -1, 1],
[-1, 0, -1, -1],
[1, 1, 0, 1],
[1, 1, 0, -1],
[1, -1, 0, 1],
[1, -1, 0, -1],
[-1, 1, 0, 1],
[-1, 1, 0, -1],
[-1, -1, 0, 1],
[-1, -1, 0, -1],
[1, 1, 1, 0],
[1, 1, -1, 0],
[1, -1, 1, 0],
[1, -1, -1, 0],
[-1, 1, 1, 0],
[-1, 1, -1, 0],
[-1, -1, 1, 0],
[-1, -1, -1, 0],
];
const F2 = (Math.sqrt(3) - 1) / 2;
const G2 = (3 - Math.sqrt(3)) / 6;
const F3 = 1 / 3;
const G3 = 1 / 6;
const F4 = (Math.sqrt(5) - 1) / 4;
const G4 = (5 - Math.sqrt(5)) / 20;
const newPerm = () => {
const perm = new Array<number>(256);
for (let index = 0; index < 256; index += 1) perm[index] = index;
return perm;
};
const NextInteger = (min: number, max: number) => Math.floor(Math.random()*(max - min + 1)) + min;
export default class Simplex {
private perm: Array<number>;
private permMod12: Array<number>;
/**
* Returns a Simplex generator with a state initialized by a random shuffle.
* @param {Array<number>?} perm An array to set as the `perm` entry in the class.
* @param {Array<number>?} permMod12 An array to set as the `permMod12` entry in the class.
*/
public constructor(perm?: Array<number>, permMod12?: Array<number>) {
const permutations = newPerm();
for (let index = permutations.length; index > 1; index -= 1) {
const jndex = NextInteger(1, index);
const swap = permutations[index];
permutations[index] = permutations[jndex];
permutations[jndex] = swap;
}
const isPermUndefined = perm === undefined;
const isPermMod12Undefined = permMod12 === undefined;
if (isPermUndefined) {
if (isPermMod12Undefined) {
this.perm = new Array<number>(512);
this.permMod12 = new Array<number>(512);
for (let index = 0; index < 512; index += 1) {
this.perm[index] = permutations[index % 256];
this.permMod12[index] = this.perm[index] % 12;
}
} else {
this.perm = new Array<number>(512);
this.permMod12 = permMod12;
for (let index = 0; index < 512; index += 1) this.perm[index] = permutations[index % 256];
}
} else {
if (isPermMod12Undefined) {
this.perm = perm;
this.permMod12 = new Array<number>(512);
for (let index = 0; index < 512; index += 1) this.permMod12[index] = this.perm[index] % 12;
} else {
this.perm = perm;
this.permMod12 = permMod12;
}
}
}
/**
* Returns a Simplex generator with a state initialized by a table of permutations.
* @param {Array<number>} permutations An array containing some permutation of each integer between 0 and 255, inclusive.
* @returns {Simplex}
*/
public static FromArray(permutations: Array<number>): Simplex {
const found = new Set<number>();
for (let index = 0; index < 256; index += 1) {
const value = permutations[index];
if (value === undefined) throw(`permutations missing index ${index}`);
found.add(value);
}
for (let index = 0; index < 256; index += 1)
if (!found.has(index)) throw(`permutations missing value ${index}`);
const perm = new Array<number>(512);
const permMod12 = new Array<number>(512);
for (let index = 0; index < 512; index += 1) {
perm[index] = permutations[index % 256];
permMod12[index] = perm[index] % 12;
}
return new Simplex(perm, permMod12);
}
/**
* Returns a Simplex generator with a state initialized by a random function.
* @param {(index: number) => number} generator A function that receives an integer, and returns an integer between 1 and the given value, inclusive. A generated array of permutations will be shuffled using this function. `math.random` is an example of such a function.
* @returns {Simplex}
*/
public static FromFunction(generator: (index: number) => number): Simplex {
const permutations = newPerm();
for (let index = permutations.length; index > 1; index -= 1) {
const jndex = generator(index);
const swap = permutations[index];
permutations[index] = permutations[jndex];
permutations[jndex] = swap;
}
const perm = new Array<number>(512);
const permMod12 = new Array<number>(512);
for (let index = 0; index < 512; index += 1) {
perm[index] = permutations[index % 256];
permMod12[index] = perm[index] % 12;
}
return new Simplex(perm, permMod12);
}
/**
* Returns a number in the interval [-1, 1] based on the given two-dimensional coordinates and the generator's permutation state.
* @param {number} x The X value to generate with.
* @param {number} y The Y value to generate with.
* @returns {number} The 2D noise value.
*/
public Noise2d(x: number, y: number): number {
const perm = this.perm;
const permMod12 = this.permMod12;
let n0, n1, n2; // Noise contributions from the three corners
// Skew the input space to determine which simplex cell we're in
const s = (x + y) * F2; // Hairy factor for 2D
const i = Math.floor(x + s);
const j = Math.floor(y + s);
const t = (i + j) * G2;
let x0 = i - t; // Unskew the cell origin back to (x,y) space
let y0 = j - t;
x0 = x - x0; // The x,y distances from the cell origin
y0 = y - y0;
// For the 2D case, the simplex shape is an equilateral triangle.
// Determine which simplex we are in.
let i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords
if (x0 > y0) {
i1 = 1;
j1 = 0; // lower triangle, xy order: (0,0)->(1,0)->(1,1)
} else {
i1 = 0;
j1 = 1; // upper triangle, yx order: (0,0)->(0,1)->(1,1)
}
// A step of (1,0) in (i,j) means a step of (1-c, -c) in (x,y), and
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where
// c = (3-sqrt(3))/6
const x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords
const y1 = y0 - j1 + G2;
const x2 = x0 - 1 + 2 * G2; // Offsets for last corner in (x,y) unskewed coords
const y2 = y0 - 1 + 2 * G2;
// Work out the hashed gradient indices of the three simplex corners
const ii = i % 256;
const jj = j % 256;
// Calculate the contribution from the three corners
let t0 = 0.5 - x0 * x0 - y0 * y0;
if (t0 < 0) n0 = 0;
else {
t0 *= t0;
const g = GRAD_3[permMod12[ii + perm[jj]]];
n0 = t0 * t0 * (g[0] * x0 + g[1] * y0); // (x,y) of GRAD_3 used for 2D gradient
}
let t1 = 0.5 - x1 * x1 - y1 * y1;
if (t1 < 0) n1 = 0;
else {
t1 *= t1;
const g = GRAD_3[permMod12[ii + i1 + perm[jj + j1]]];
n1 = t1 * t1 * (g[0] * x1 + g[1] * y1);
}
let t2 = 0.5 - x2 * x2 - y2 * y2;
if (t2 < 0) n2 = 0;
else {
t2 *= t2;
const g = GRAD_3[permMod12[ii + 1 + perm[1 + jj]]];
n2 = t2 * t2 * (g[0] * x2 + g[1] * y2);
}
// Add contributions from each corner to get the final noise value.
// The result is scaled to return values in the interval [-1,1].
return 70 * (n0 + n1 + n2);
}
/**
* Returns a number in the interval [-1, 1] based on the given three-dimensional coordinates and the generator's permutation state.
* @param {number} x The X value to generate with.
* @param {number} y The Y value to generate with.
* @param {number} z The Z value to generate with.
* @returns {number} The 3D noise value.
*/
public Noise3d(x: number, y: number, z: number): number {
const perm = this.perm;
const permMod12 = this.permMod12;
let n0, n1, n2, n3; // Noise contributions from the four corners
// Skew the input space to determine which simplex cell we're in
const s = (x + y + z) * F3; // Very nice and simple skew factor for 3D
const i = Math.floor(x + s);
const j = Math.floor(y + s);
const k = Math.floor(z + s);
const t = (i + j + k) * G3;
const X0 = i - t; // Unskew the cell origin back to (x,y,z) space
const Y0 = j - t;
const Z0 = k - t;
const x0 = x - X0; // The x,y,z distances from the cell origin
const y0 = y - Y0;
const z0 = z - Z0;
// For the 3D case, the simplex shape is a slightly irregular tetrahedron.
// Determine which simplex we are in.
let i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords
let i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords
if (x0 >= y0) {
if (y0 >= z0) {
// X Y Z order
i1 = 1;
j1 = 0;
k1 = 0;
i2 = 1;
j2 = 1;
k2 = 0;
} else if (x0 >= z0) {
// X Z Y order
i1 = 1;
j1 = 0;
k1 = 0;
i2 = 1;
j2 = 0;
k2 = 1;
} else {
// Z X Y order
i1 = 0;
j1 = 0;
k1 = 1;
i2 = 1;
j2 = 0;
k2 = 1;
}
} else {
// x0 < y0
if (y0 < z0) {
// Z Y X order
i1 = 0;
j1 = 0;
k1 = 1;
i2 = 0;
j2 = 1;
k2 = 1;
} else if (x0 < z0) {
// Y Z X order
i1 = 0;
j1 = 1;
k1 = 0;
i2 = 0;
j2 = 1;
k2 = 1;
} else {
// Y X Z order
i1 = 0;
j1 = 1;
k1 = 0;
i2 = 1;
j2 = 1;
k2 = 0;
}
}
// 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.
const x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords
const y1 = y0 - j1 + G3;
const z1 = z0 - k1 + G3;
const x2 = x0 - i2 + 2 * G3; // Offsets for third corner in (x,y,z) coords
const y2 = y0 - j2 + 2 * G3;
const z2 = z0 - k2 + 2 * G3;
const x3 = x0 - 1 + 3 * G3; // Offsets for last corner in (x,y,z) coords
const y3 = y0 - 1 + 3 * G3;
const z3 = z0 - 1 + 3 * G3;
// Work out the hashed gradient indices of the four simplex corners
const ii = i % 256;
const jj = j % 256;
const kk = k % 256;
// Calculate the contribution from the four corners
let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0;
if (t0 < 0) n0 = 0;
else {
t0 *= t0;
const g = GRAD_3[permMod12[ii + perm[jj + perm[kk]]]];
n0 = t0 * t0 * (g[0] * x0 + g[1] * y0 + g[2] * z0);
}
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1;
if (t1 < 0) n1 = 0;
else {
t1 *= t1;
const g = GRAD_3[permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]]];
n1 = t1 * t1 * (g[0] * x1 + g[1] * y1 + g[2] * z1);
}
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2;
if (t2 < 0) n2 = 0;
else {
t2 *= t2;
const g = GRAD_3[permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]]];
n2 = t2 * t2 * (g[0] * x2 + g[1] * y2 + g[2] * z2);
}
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3;
if (t3 < 0) n3 = 0;
else {
t3 *= t3;
const g = GRAD_3[permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]]];
n3 = t3 * t3 * (g[0] * x3 + g[1] * y3 + g[2] * z3);
}
// Add contributions from each corner to get the final noise value.
// The result is scaled to stay just inside [-1,1]
return 32 * (n0 + n1 + n2 + n3);
}
/**
* Returns a number in the interval [-1, 1] based on the given four-dimensional coordinates and the generator's permutation state.
* @param {number} x The X to generate with.
* @param {number} y The Y to generate with.
* @param {number} z The Z to generate with.
* @param {number} w The W to generate with.
* @returns {number} The 4D noise value.
*/
public Noise4d(x: number, y: number, z: number, w: number): number {
const perm = this.perm;
let n0, n1, n2, n3, n4; // Noise contributions from the five corners
// Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in
const s = (x + y + z + w) * F4; // Factor for 4D skewing
const i = Math.floor(x + s);
const j = Math.floor(y + s);
const k = Math.floor(z + s);
const l = Math.floor(w + s);
const t = (i + j + k + l) * G4; // Factor for 4D unskewing
const X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space
const Y0 = j - t;
const Z0 = k - t;
const W0 = l - t;
const x0 = x - X0; // The x,y,z,w distances from the cell origin
const y0 = y - Y0;
const z0 = z - Z0;
const w0 = w - W0;
// For the 4D case, the simplex is a 4D shape I won't even try to describe.
// To find out which of the 24 possible simplices we're in, we need to
// determine the magnitude ordering of x0, y0, z0 and w0.
// Six pair-wise comparisons are performed between each possible pair
// of the four coordinates, and the results are used to rank the numbers.
let rankx = 0;
let ranky = 0;
let rankz = 0;
let rankw = 0;
if (x0 > y0) rankx += 1;
else ranky += 1;
if (x0 > z0) rankx += 1;
else rankz += 1;
if (x0 > w0) rankx += 1;
else rankw += 1;
if (y0 > z0) ranky += 1;
else rankz += 1;
if (y0 > w0) ranky += 1;
else rankw += 1;
if (z0 > w0) rankz += 1;
else rankw += 1;
// simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order.
// Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w
// impossible. Only the 24 indices which have non-zero entries make any sense.
// We use a thresholding to set the coordinates in turn from the largest magnitude.
// The integer offsets for the second simplex corner
// Rank 3 denotes the largest coordinate.
const i1 = rankx >= 3 ? 1 : 0;
const j1 = ranky >= 3 ? 1 : 0;
const k1 = rankz >= 3 ? 1 : 0;
const l1 = rankw >= 3 ? 1 : 0;
// The integer offsets for the third simplex corner
// Rank 2 denotes the second largest coordinate.
const i2 = rankx >= 2 ? 1 : 0;
const j2 = ranky >= 2 ? 1 : 0;
const k2 = rankz >= 2 ? 1 : 0;
const l2 = rankw >= 2 ? 1 : 0;
// The integer offsets for the fourth simplex corner
// Rank 1 denotes the second smallest coordinate.
const i3 = rankx >= 1 ? 1 : 0;
const j3 = ranky >= 1 ? 1 : 0;
const k3 = rankz >= 1 ? 1 : 0;
const l3 = rankw >= 1 ? 1 : 0;
// The fifth corner has all coordinate offsets = 1, so no need to compute that.
const x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords
const y1 = y0 - j1 + G4;
const z1 = z0 - k1 + G4;
const w1 = w0 - l1 + G4;
const x2 = x0 - i2 + 2 * G4; // Offsets for third corner in (x,y,z,w) coords
const y2 = y0 - j2 + 2 * G4;
const z2 = z0 - k2 + 2 * G4;
const w2 = w0 - l2 + 2 * G4;
const x3 = x0 - i3 + 3 * G4; // Offsets for fourth corner in (x,y,z,w) coords
const y3 = y0 - j3 + 3 * G4;
const z3 = z0 - k3 + 3 * G4;
const w3 = w0 - l3 + 3 * G4;
const x4 = x0 - 1 + 4 * G4; // Offsets for last corner in (x,y,z,w) coords
const y4 = y0 - 1 + 4 * G4;
const z4 = z0 - 1 + 4 * G4;
const w4 = w0 - 1 + 4 * G4;
// Work out the hashed gradient indices of the five simplex corners
const ii = i % 256;
const jj = j % 256;
const kk = k % 256;
const ll = l % 256;
// Calculate the contribution from the five corners
let t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0;
if (t0 < 0) n0 = 0;
else {
t0 *= t0;
const g = GRAD_4[perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32];
n0 = t0 * t0 * (g[0] * x0 + g[1] * y0 + g[2] * z0 + g[3] * w0);
}
let t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1;
if (t1 < 0) n1 = 0;
else {
t1 *= t1;
const g = GRAD_4[perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32];
n1 = t1 * t1 * (g[0] * x1 + g[1] * y1 + g[2] * z1 + g[3] * w1);
}
let t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2;
if (t2 < 0) n2 = 0;
else {
t2 *= t2;
const g = GRAD_4[perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32];
n2 = t2 * t2 * (g[0] * x2 + g[1] * y2 + g[2] * z2 + g[3] * w2);
}
let t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3;
if (t3 < 0) n3 = 0;
else {
t3 *= t3;
const g = GRAD_4[perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32];
n3 = t3 * t3 * (g[0] * x3 + g[1] * y3 + g[2] * z3 + g[3] * w3);
}
let t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4;
if (t4 < 0) n4 = 0;
else {
t4 *= t4;
const g = GRAD_4[perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32];
n4 = t4 * t4 * (g[0] * x4 + g[1] * y4 + g[2] * z4 + g[3] * w4);
}
// Sum up and scale the result to cover the range [-1,1]
return 27 * (n0 + n1 + n2 + n3 + n4);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment