mirror of
https://gitlab.com/freepascal.org/fpc/pas2js.git
synced 2025-04-05 08:57:49 +02:00
245 lines
6.3 KiB
ObjectPascal
245 lines
6.3 KiB
ObjectPascal
unit Noise;
|
|
interface
|
|
uses
|
|
Math;
|
|
|
|
const
|
|
kNoisekPerumationMax = 256;
|
|
|
|
type
|
|
TNoiseValue = byte;
|
|
TNoiseSeedArray = array[0..kNoisekPerumationMax-1] of TNoiseValue;
|
|
TNoiseFloat = double;
|
|
|
|
type
|
|
TNoise = class (TObject)
|
|
public
|
|
constructor Create; overload;
|
|
constructor Create (seed: TNoiseSeedArray); overload;
|
|
|
|
function GetValue (x, y, z: TNoiseFloat): TNoiseFloat; overload;
|
|
function GetValue (x, y, z: TNoiseFloat; octaves: integer; persistence: TNoiseFloat): TNoiseFloat; overload;
|
|
function GetNoise (x, y: integer; width, height: integer; scale: TNoiseFloat; frequency: integer): TNoiseFloat; overload;
|
|
|
|
private
|
|
repeatValue: integer;
|
|
p: array[0..(kNoisekPerumationMax * 2)-1] of TNoiseValue;
|
|
|
|
function Inc (num: integer): integer; inline;
|
|
function Grad (hash: integer; x, y, z: TNoiseFloat): TNoiseFloat; inline;
|
|
function Fade (t: TNoiseFloat): TNoiseFloat; inline;
|
|
function Lerp (a, b, x: TNoiseFloat): TNoiseFloat; inline;
|
|
end;
|
|
|
|
function RandomNoiseSeed (seed: cardinal = 0): TNoiseSeedArray;
|
|
|
|
implementation
|
|
|
|
|
|
function RandomNoiseSeed (seed: cardinal = 0): TNoiseSeedArray;
|
|
var
|
|
i: integer;
|
|
begin
|
|
for i := 0 to kNoisekPerumationMax - 1 do
|
|
result[i] := Random(kNoisekPerumationMax);
|
|
end;
|
|
|
|
function TNoise.GetValue (x, y, z: TNoiseFloat; octaves: integer; persistence: TNoiseFloat): TNoiseFloat;
|
|
var
|
|
total: TNoiseFloat = 0;
|
|
frequency: TNoiseFloat = 1;
|
|
amplitude: TNoiseFloat = 1;
|
|
maxValue: TNoiseFloat = 0; // Used for normalizing result to 0.0 - 1.0
|
|
i: integer;
|
|
begin
|
|
for i := 0 to octaves - 1 do
|
|
begin
|
|
total += GetValue(x * frequency, y * frequency, z * frequency) * amplitude;
|
|
maxValue += amplitude;
|
|
amplitude *= persistence;
|
|
frequency *= 2;
|
|
end;
|
|
result := total/maxValue;
|
|
end;
|
|
|
|
function TNoise.GetNoise (x, y: integer; width, height: integer; scale: TNoiseFloat; frequency: integer): TNoiseFloat;
|
|
var
|
|
nx, ny: TNoiseFloat;
|
|
begin
|
|
nx := x/width - 0.5;
|
|
ny := y/height - 0.5;
|
|
result := GetValue(nx * scale, ny * scale, 0, frequency, 0.5) / 2 + 0.5;
|
|
end;
|
|
|
|
function TNoise.GetValue (x, y, z: TNoiseFloat): TNoiseFloat;
|
|
function FMod(const a, b: TNoiseFloat): TNoiseFloat;
|
|
begin
|
|
result:= a-b * trunc(a/b);
|
|
end;
|
|
var
|
|
xi, yi, zi: integer;
|
|
xf, yf, zf: TNoiseFloat;
|
|
u, v, w: TNoiseFloat;
|
|
aaa, aba, aab, abb, baa, bba, bab, bbb: integer;
|
|
x1, x2, y1, y2: TNoiseFloat;
|
|
begin
|
|
// If we have any repeat on, change the coordinates to their "local" repetitions
|
|
if (repeatValue > 0) then
|
|
begin
|
|
x := FMod(x, repeatValue);
|
|
y := FMod(y, repeatValue);
|
|
z := FMod(z, repeatValue);
|
|
// ??? mod overloading for singles in trunk 3.1.1
|
|
{x := x mod repeatValue;
|
|
y := y mod repeatValue;
|
|
z := z mod repeatValue;}
|
|
end;
|
|
|
|
xi := Floor(x) and 255; // Calculate the "unit cube" that the point asked will be located in
|
|
yi := Floor(y) and 255; // The left bound is ( |_x_|,|_y_|,|_z_| ) and the right bound is that
|
|
zi := Floor(z) and 255; // plus 1. Next we calculate the location (from 0.0 to 1.0) in that cube.
|
|
xf := x-Floor(x); // We also fade the location to smooth the result.
|
|
yf := y-Floor(y);
|
|
zf := z-Floor(z);
|
|
u := Fade(xf);
|
|
v := Fade(yf);
|
|
w := Fade(zf);
|
|
|
|
aaa := p[p[p[ xi ]+ yi ]+ zi ];
|
|
aba := p[p[p[ xi ]+self.Inc(yi)]+ zi ];
|
|
aab := p[p[p[ xi ]+ yi ]+self.Inc(zi)];
|
|
abb := p[p[p[ xi ]+self.Inc(yi)]+self.Inc(zi)];
|
|
baa := p[p[p[self.Inc(xi)]+ yi ]+ zi ];
|
|
bba := p[p[p[self.Inc(xi)]+self.Inc(yi)]+ zi ];
|
|
bab := p[p[p[self.Inc(xi)]+ yi ]+self.Inc(zi)];
|
|
bbb := p[p[p[self.Inc(xi)]+self.Inc(yi)]+self.Inc(zi)];
|
|
|
|
x1 := Lerp( Grad(aaa, xf , yf , zf), // The gradient function calculates the dot product between a pseudorandom
|
|
Grad(baa, xf-1, yf , zf), // gradient vector and the vector from the input coordinate to the 8
|
|
u); // surrounding points in its unit cube.
|
|
x2 := Lerp( Grad(aba, xf , yf-1, zf), // This is all then lerped together as a sort of weighted average based on the faded (u,v,w)
|
|
Grad(bba, xf-1, yf-1, zf), // values we made earlier.
|
|
u);
|
|
y1 := Lerp(x1, x2, v);
|
|
|
|
x1 := Lerp( Grad(aab, xf , yf , zf-1),
|
|
Grad(bab, xf-1, yf , zf-1),
|
|
u);
|
|
x2 := Lerp( Grad(abb, xf , yf-1, zf-1),
|
|
Grad(bbb, xf-1, yf-1, zf-1),
|
|
u);
|
|
y2 := Lerp(x1, x2, v);
|
|
|
|
result := (Lerp(y1, y2, w)+1)/2; // For convenience we bound it to 0 - 1 (theoretical min/max before is -1 - 1)
|
|
end;
|
|
|
|
function TNoise.Inc (num: integer): integer;
|
|
begin
|
|
num += 1;
|
|
if repeatValue > 0 then
|
|
num := num mod repeatValue;
|
|
result := num;
|
|
end;
|
|
|
|
// http://riven8192.blogspot.com/2010/08/calculate-perlinnoise-twice-as-fast.html
|
|
function TNoise.Grad (hash: integer; x, y, z: TNoiseFloat): TNoiseFloat;
|
|
begin
|
|
case (hash and $F) of
|
|
$0:
|
|
result := x + y;
|
|
$1:
|
|
result := -x + y;
|
|
$2:
|
|
result := x - y;
|
|
$3:
|
|
result := -x - y;
|
|
$4:
|
|
result := x + z;
|
|
$5:
|
|
result := -x + z;
|
|
$6:
|
|
result := x - z;
|
|
$7:
|
|
result := -x - z;
|
|
$8:
|
|
result := y + z;
|
|
$9:
|
|
result := -y + z;
|
|
$A:
|
|
result := y - z;
|
|
$B:
|
|
result := -y - z;
|
|
$C:
|
|
result := y + x;
|
|
$D:
|
|
result := -y + z;
|
|
$E:
|
|
result := y - x;
|
|
$F:
|
|
result := -y - z;
|
|
otherwise
|
|
result := 0; // never happens
|
|
end;
|
|
end;
|
|
|
|
{
|
|
function TNoise.Grad (hash: integer; x, y, z: TNoiseFloat): TNoiseFloat;
|
|
var
|
|
h: integer;
|
|
u, v: TNoiseFloat;
|
|
begin
|
|
h := hash and 15; // Take the hashed value and take the first 4 bits of it (15 == 0b1111)
|
|
|
|
if h < 8 then
|
|
u := x
|
|
else
|
|
u := y;
|
|
|
|
if h < 4 then
|
|
v := y
|
|
else if (h = 12) or (h = 14) then
|
|
v := x
|
|
else
|
|
v := z;
|
|
|
|
if h and 1 = 0 then
|
|
result := u
|
|
else
|
|
result := -u;
|
|
|
|
if h and 2 = 0 then
|
|
result := result + v
|
|
else
|
|
result := result - v;
|
|
end;
|
|
}
|
|
|
|
function TNoise.Fade (t: TNoiseFloat): TNoiseFloat;
|
|
begin
|
|
// 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.
|
|
result := t * t * t * (t * (t * 6 - 15) + 10); // 6t^5 - 15t^4 + 10t^3
|
|
end;
|
|
|
|
function TNoise.Lerp (a, b, x: TNoiseFloat): TNoiseFloat;
|
|
begin
|
|
result := a + x * (b - a);
|
|
end;
|
|
|
|
constructor TNoise.Create;
|
|
begin
|
|
Create(RandomNoiseSeed);
|
|
end;
|
|
|
|
constructor TNoise.Create (seed: TNoiseSeedArray);
|
|
var
|
|
i: integer;
|
|
begin
|
|
repeatValue := -1;
|
|
for i := 0 to high(p) do
|
|
p[i] := seed[i mod kNoisekPerumationMax];
|
|
end;
|
|
|
|
end.
|