123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337 |
- unit GBE.Terrain;
- interface
- uses
- System.SysUtils,
- System.Classes,
- System.Types,
- System.Math.Vectors,
- System.RTLConsts,
- FMX.Types,
- FMX.Types3D,
- FMX.Controls3D,
- FMX.Objects3D,
- FMX.Graphics,
- Math,
- uGBEUtils3D;
- type
- TGBETerrain = class(TMesh)
- private
- fAmplitude, fRoughness, fScalling: single;
- fOctaves, fSubdivX, fSubdivZ: integer;
- fSeed, fXOffset, fZOffset: integer;
- FUseRamp: boolean;
- function GetInterpolatedNoise(X, Z: single): single;
- function Interpolate(a, b, blend: single): single;
- function Noise(X, Z: integer): single;
- function SmoothNoise(X, Z: integer): single;
- procedure SetUseRamp(const Value: boolean);
- function GenerateHeight(X, Z: integer): single;
- protected
- public
- constructor Create(AOwner: TComponent); override;
- destructor Destroy; override;
- procedure Clean;
- procedure GenerateTerrain;
- function GetHeight(P: TPoint3d): single;
- published
- property Amplitude: single read fAmplitude write fAmplitude;
- property Roughness: single read fRoughness write fRoughness;
- property Octaves: integer read fOctaves write fOctaves;
- property XOffset: integer read fXOffset write fXOffset;
- property ZOffset: integer read fZOffset write fZOffset;
- property Seed: integer read fSeed write fSeed;
- property SubdivX: integer read fSubdivX write fSubdivX;
- property SubdivZ: integer read fSubdivZ write fSubdivZ;
- property UseRamp: boolean read FUseRamp write SetUseRamp;
- end;
- procedure Register;
- implementation //--------------------------------------------------------------
- // TGBETerrain
- function TGBETerrain.GenerateHeight(X, Z: integer): single;
- begin
- var
- total := 0.0;
- var
- d := Math.Power(2, fOctaves - 1);
- for var i := 0 to fOctaves - 1 do
- begin
- var
- freq := Math.Power(2, i) / d;
- var
- amp := Math.Power(fRoughness, i) * fAmplitude;
- total := total + GetInterpolatedNoise((X + fXOffset) * freq,
- (Z + fZOffset) * freq) * amp;
- end;
- result := total;
- end;
- (*
- GetInterpolatedNoise return an interpolate value for the height
- of a position at x and z coordinates
- *)
- function TGBETerrain.GetInterpolatedNoise(X, Z: single): single;
- begin
- var
- intX: integer := trunc(X);
- var
- intZ: integer := trunc(Z);
- var
- fracX := X - intX;
- var
- fracZ := Z - intZ;
- // use the near neighbours points v1, v2, v3, v4
- var
- v1 := SmoothNoise(intX, intZ);
- var
- v2 := SmoothNoise(intX + 1, intZ);
- var
- v3 := SmoothNoise(intX, intZ + 1);
- var
- v4 := SmoothNoise(intX + 1, intZ + 1);
- (*
- X is the point with x,z coordinates
- v1--------i1---v2
- | . |
- | X |
- | . |
- | . |
- | . |
- v3--------i2---v4
- *)
- var
- i1 := Interpolate(v1, v2, fracX);
- var
- i2 := Interpolate(v3, v4, fracX);
- // result interpolate i1 and i2
- result := Interpolate(i1, i2, fracZ);
- end;
- (*
- Cosine interpolation to be more natural
- return an interpolate value between 2 values a and b
- *)
- function TGBETerrain.Interpolate(a, b, blend: single): single;
- begin
- var
- theta := blend * PI;
- var
- f := (1.0 - cos(theta)) * 0.5;
- result := a * (1.0 - f) + b * f;
- end;
- // smoothNoise use the noise function and the neighbours vertices from a specific vertex
- function TGBETerrain.SmoothNoise(X, Z: integer): single;
- begin
- var
- corners := (Noise(X - 1, Z - 1) + Noise(X + 1, Z - 1) + Noise(X - 1, Z + 1) +
- Noise(X + 1, Z + 1)) * 0.125;
- var
- sides := (Noise(X - 1, Z) + Noise(X + 1, Z) + Noise(X, Z - 1) + Noise(X,
- Z + 1)) * 0.25;
- var
- center := Noise(X, Z) * 0.5;
- result := corners + sides + center;
- end;
- // Nose function is a pure function to return a random number between -1 and 1
- function TGBETerrain.Noise(X, Z: integer): single;
- begin
- randSeed := X * 9158 + Z * 41765 + fSeed; // seed value for random
- result := random * 2.0 - 1.0;
- // random return a number between 0 and 1 and we want a number between -1 and 1
- end;
- procedure TGBETerrain.Clean;
- begin
- Data.Clear;
- end;
- constructor TGBETerrain.Create(AOwner: TComponent);
- begin
- inherited;
- fSeed := random(9999999);
- fXOffset := 0;
- fZOffset := 0;
- UseRamp := false;
- HitTest := false;
- end;
- destructor TGBETerrain.Destroy;
- begin
- inherited;
- end;
- // generate procedural terrain
- procedure TGBETerrain.GenerateTerrain;
- begin
- var
- NP := 0;
- var
- NI := 0;
- var
- yMin := 0.0;
- var
- yMax := 0.0;
- var
- vertexArray: TArray<TPoint3d>;;
- fOctaves := Octaves;
- fAmplitude := Amplitude;
- fRoughness := Roughness;
- try
- Data.VertexBuffer.Length := Round(SubdivX * SubdivZ * 4);
- setLength(vertexArray, Data.VertexBuffer.Length);
- Data.IndexBuffer.Length := Round(SubdivX * SubdivZ * 6);
- // Initialize vertexArray and compute Y for each vertex
- var
- v := 0.0;
- while v < SubdivZ do
- begin
- var
- u := 0.0;
- while u < SubdivX do
- begin
- vertexArray[NP + 0].X := u;
- vertexArray[NP + 0].Z := v;
- vertexArray[NP + 0].Y :=
- GenerateHeight(trunc(vertexArray[NP + 0].X + fXOffset),
- trunc(vertexArray[NP + 0].Z + fZOffset));
- if vertexArray[NP + 0].Y < yMin then
- yMin := vertexArray[NP + 0].Y;
- if vertexArray[NP + 0].Y > yMax then
- yMax := vertexArray[NP + 0].Y;
- vertexArray[NP + 1].X := u + 1;
- vertexArray[NP + 1].Z := v;
- vertexArray[NP + 1].Y :=
- GenerateHeight(trunc(vertexArray[NP + 1].X + fXOffset),
- trunc(vertexArray[NP + 1].Z + fZOffset));
- if vertexArray[NP + 1].Y < yMin then
- yMin := vertexArray[NP + 1].Y;
- if vertexArray[NP + 1].Y > yMax then
- yMax := vertexArray[NP + 1].Y;
- vertexArray[NP + 2].X := u + 1;
- vertexArray[NP + 2].Z := v + 1;
- vertexArray[NP + 2].Y :=
- GenerateHeight(trunc(vertexArray[NP + 2].X + fXOffset),
- trunc(vertexArray[NP + 2].Z + fZOffset));
- if vertexArray[NP + 2].Y < yMin then
- yMin := vertexArray[NP + 2].Y;
- if vertexArray[NP + 2].Y > yMax then
- yMax := vertexArray[NP + 2].Y;
- vertexArray[NP + 3].X := u;
- vertexArray[NP + 3].Z := v + 1;
- vertexArray[NP + 3].Y :=
- GenerateHeight(trunc(vertexArray[NP + 3].X + fXOffset),
- trunc(vertexArray[NP + 3].Z + fZOffset));
- if vertexArray[NP + 3].Y < yMin then
- yMin := vertexArray[NP + 3].Y;
- if vertexArray[NP + 3].Y > yMax then
- yMax := vertexArray[NP + 3].Y;
- NP := NP + 4;
- u := u + 1;
- end;
- v := v + 1;
- end;
- if yMax - yMin > 0 then
- fScalling := self.Height / (yMax - yMin)
- else
- fScalling := 1;
- var
- heightToColor := 255 / (abs(yMin) + abs(yMax)) / 255;
- var
- i := 0;
- NP := 0;
- while i < Length(vertexArray) - 3 do
- begin
- with Data do
- begin
- with VertexBuffer do
- begin
- Vertices[NP + 0] := vertexArray[i + 0];
- Vertices[NP + 1] := vertexArray[i + 1];
- Vertices[NP + 2] := vertexArray[i + 2];
- Vertices[NP + 3] := vertexArray[i + 3];
- if UseRamp then
- begin
- TexCoord0[NP + 0] := PointF((vertexArray[i + 0].Y + abs(yMin)) *
- heightToColor, 0);
- TexCoord0[NP + 1] := PointF((vertexArray[i + 1].Y + abs(yMin)) *
- heightToColor, 0);
- TexCoord0[NP + 2] := PointF((vertexArray[i + 2].Y + abs(yMin)) *
- heightToColor, 0);
- TexCoord0[NP + 3] := PointF((vertexArray[i + 3].Y + abs(yMin)) *
- heightToColor, 0);
- end
- else
- begin
- TexCoord0[NP + 0] := PointF((vertexArray[i + 0].X) / SubdivX,
- (vertexArray[i + 0].Z) / SubdivZ);
- TexCoord0[NP + 1] := PointF((vertexArray[i + 1].X) / SubdivX,
- (vertexArray[i + 1].Z) / SubdivZ);
- TexCoord0[NP + 2] := PointF((vertexArray[i + 2].X) / SubdivX,
- (vertexArray[i + 2].Z) / SubdivZ);
- TexCoord0[NP + 3] := PointF((vertexArray[i + 3].X) / SubdivX,
- (vertexArray[i + 3].Z) / SubdivZ);
- end;
- end;
- IndexBuffer[NI + 0] := NP + 0;
- IndexBuffer[NI + 1] := NP + 1;
- IndexBuffer[NI + 2] := NP + 3;
- IndexBuffer[NI + 3] := NP + 3;
- IndexBuffer[NI + 4] := NP + 1;
- IndexBuffer[NI + 5] := NP + 2;
- end;
- NP := NP + 4;
- NI := NI + 6;
- inc(i, 4);
- end;
- Data.CalcTangentBinormals;
- finally
- end;
- end;
- procedure TGBETerrain.SetUseRamp(const Value: boolean);
- begin
- if Value <> FUseRamp then
- FUseRamp := Value;
- end;
- function TGBETerrain.GetHeight(P: TPoint3d): single;
- begin
- result := CalculateHeight(self, P, fScalling, fSubdivX, fSubdivZ);
- end;
- //---------------------------------------------------------------------------
- procedure Register;
- begin
- RegisterComponents('GXScene GBE', [TGBETerrain]);
- end;
- end.
|