GBE.Terrain.pas 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. unit GBE.Terrain;
  2. interface
  3. uses
  4. System.SysUtils,
  5. System.Classes,
  6. System.Types,
  7. System.Math.Vectors,
  8. System.RTLConsts,
  9. FMX.Types,
  10. FMX.Types3D,
  11. FMX.Controls3D,
  12. FMX.Objects3D,
  13. FMX.Graphics,
  14. Math,
  15. uGBEUtils3D;
  16. type
  17. TGBETerrain = class(TMesh)
  18. private
  19. fAmplitude, fRoughness, fScalling: single;
  20. fOctaves, fSubdivX, fSubdivZ: integer;
  21. fSeed, fXOffset, fZOffset: integer;
  22. FUseRamp: boolean;
  23. function GetInterpolatedNoise(X, Z: single): single;
  24. function Interpolate(a, b, blend: single): single;
  25. function Noise(X, Z: integer): single;
  26. function SmoothNoise(X, Z: integer): single;
  27. procedure SetUseRamp(const Value: boolean);
  28. function GenerateHeight(X, Z: integer): single;
  29. protected
  30. public
  31. constructor Create(AOwner: TComponent); override;
  32. destructor Destroy; override;
  33. procedure Clean;
  34. procedure GenerateTerrain;
  35. function GetHeight(P: TPoint3d): single;
  36. published
  37. property Amplitude: single read fAmplitude write fAmplitude;
  38. property Roughness: single read fRoughness write fRoughness;
  39. property Octaves: integer read fOctaves write fOctaves;
  40. property XOffset: integer read fXOffset write fXOffset;
  41. property ZOffset: integer read fZOffset write fZOffset;
  42. property Seed: integer read fSeed write fSeed;
  43. property SubdivX: integer read fSubdivX write fSubdivX;
  44. property SubdivZ: integer read fSubdivZ write fSubdivZ;
  45. property UseRamp: boolean read FUseRamp write SetUseRamp;
  46. end;
  47. procedure Register;
  48. implementation //--------------------------------------------------------------
  49. // TGBETerrain
  50. function TGBETerrain.GenerateHeight(X, Z: integer): single;
  51. begin
  52. var
  53. total := 0.0;
  54. var
  55. d := Math.Power(2, fOctaves - 1);
  56. for var i := 0 to fOctaves - 1 do
  57. begin
  58. var
  59. freq := Math.Power(2, i) / d;
  60. var
  61. amp := Math.Power(fRoughness, i) * fAmplitude;
  62. total := total + GetInterpolatedNoise((X + fXOffset) * freq,
  63. (Z + fZOffset) * freq) * amp;
  64. end;
  65. result := total;
  66. end;
  67. (*
  68. GetInterpolatedNoise return an interpolate value for the height
  69. of a position at x and z coordinates
  70. *)
  71. function TGBETerrain.GetInterpolatedNoise(X, Z: single): single;
  72. begin
  73. var
  74. intX: integer := trunc(X);
  75. var
  76. intZ: integer := trunc(Z);
  77. var
  78. fracX := X - intX;
  79. var
  80. fracZ := Z - intZ;
  81. // use the near neighbours points v1, v2, v3, v4
  82. var
  83. v1 := SmoothNoise(intX, intZ);
  84. var
  85. v2 := SmoothNoise(intX + 1, intZ);
  86. var
  87. v3 := SmoothNoise(intX, intZ + 1);
  88. var
  89. v4 := SmoothNoise(intX + 1, intZ + 1);
  90. (*
  91. X is the point with x,z coordinates
  92. v1--------i1---v2
  93. | . |
  94. | X |
  95. | . |
  96. | . |
  97. | . |
  98. v3--------i2---v4
  99. *)
  100. var
  101. i1 := Interpolate(v1, v2, fracX);
  102. var
  103. i2 := Interpolate(v3, v4, fracX);
  104. // result interpolate i1 and i2
  105. result := Interpolate(i1, i2, fracZ);
  106. end;
  107. (*
  108. Cosine interpolation to be more natural
  109. return an interpolate value between 2 values a and b
  110. *)
  111. function TGBETerrain.Interpolate(a, b, blend: single): single;
  112. begin
  113. var
  114. theta := blend * PI;
  115. var
  116. f := (1.0 - cos(theta)) * 0.5;
  117. result := a * (1.0 - f) + b * f;
  118. end;
  119. // smoothNoise use the noise function and the neighbours vertices from a specific vertex
  120. function TGBETerrain.SmoothNoise(X, Z: integer): single;
  121. begin
  122. var
  123. corners := (Noise(X - 1, Z - 1) + Noise(X + 1, Z - 1) + Noise(X - 1, Z + 1) +
  124. Noise(X + 1, Z + 1)) * 0.125;
  125. var
  126. sides := (Noise(X - 1, Z) + Noise(X + 1, Z) + Noise(X, Z - 1) + Noise(X,
  127. Z + 1)) * 0.25;
  128. var
  129. center := Noise(X, Z) * 0.5;
  130. result := corners + sides + center;
  131. end;
  132. // Nose function is a pure function to return a random number between -1 and 1
  133. function TGBETerrain.Noise(X, Z: integer): single;
  134. begin
  135. randSeed := X * 9158 + Z * 41765 + fSeed; // seed value for random
  136. result := random * 2.0 - 1.0;
  137. // random return a number between 0 and 1 and we want a number between -1 and 1
  138. end;
  139. procedure TGBETerrain.Clean;
  140. begin
  141. Data.Clear;
  142. end;
  143. constructor TGBETerrain.Create(AOwner: TComponent);
  144. begin
  145. inherited;
  146. fSeed := random(9999999);
  147. fXOffset := 0;
  148. fZOffset := 0;
  149. UseRamp := false;
  150. HitTest := false;
  151. end;
  152. destructor TGBETerrain.Destroy;
  153. begin
  154. inherited;
  155. end;
  156. // generate procedural terrain
  157. procedure TGBETerrain.GenerateTerrain;
  158. begin
  159. var
  160. NP := 0;
  161. var
  162. NI := 0;
  163. var
  164. yMin := 0.0;
  165. var
  166. yMax := 0.0;
  167. var
  168. vertexArray: TArray<TPoint3d>;;
  169. fOctaves := Octaves;
  170. fAmplitude := Amplitude;
  171. fRoughness := Roughness;
  172. try
  173. Data.VertexBuffer.Length := Round(SubdivX * SubdivZ * 4);
  174. setLength(vertexArray, Data.VertexBuffer.Length);
  175. Data.IndexBuffer.Length := Round(SubdivX * SubdivZ * 6);
  176. // Initialize vertexArray and compute Y for each vertex
  177. var
  178. v := 0.0;
  179. while v < SubdivZ do
  180. begin
  181. var
  182. u := 0.0;
  183. while u < SubdivX do
  184. begin
  185. vertexArray[NP + 0].X := u;
  186. vertexArray[NP + 0].Z := v;
  187. vertexArray[NP + 0].Y :=
  188. GenerateHeight(trunc(vertexArray[NP + 0].X + fXOffset),
  189. trunc(vertexArray[NP + 0].Z + fZOffset));
  190. if vertexArray[NP + 0].Y < yMin then
  191. yMin := vertexArray[NP + 0].Y;
  192. if vertexArray[NP + 0].Y > yMax then
  193. yMax := vertexArray[NP + 0].Y;
  194. vertexArray[NP + 1].X := u + 1;
  195. vertexArray[NP + 1].Z := v;
  196. vertexArray[NP + 1].Y :=
  197. GenerateHeight(trunc(vertexArray[NP + 1].X + fXOffset),
  198. trunc(vertexArray[NP + 1].Z + fZOffset));
  199. if vertexArray[NP + 1].Y < yMin then
  200. yMin := vertexArray[NP + 1].Y;
  201. if vertexArray[NP + 1].Y > yMax then
  202. yMax := vertexArray[NP + 1].Y;
  203. vertexArray[NP + 2].X := u + 1;
  204. vertexArray[NP + 2].Z := v + 1;
  205. vertexArray[NP + 2].Y :=
  206. GenerateHeight(trunc(vertexArray[NP + 2].X + fXOffset),
  207. trunc(vertexArray[NP + 2].Z + fZOffset));
  208. if vertexArray[NP + 2].Y < yMin then
  209. yMin := vertexArray[NP + 2].Y;
  210. if vertexArray[NP + 2].Y > yMax then
  211. yMax := vertexArray[NP + 2].Y;
  212. vertexArray[NP + 3].X := u;
  213. vertexArray[NP + 3].Z := v + 1;
  214. vertexArray[NP + 3].Y :=
  215. GenerateHeight(trunc(vertexArray[NP + 3].X + fXOffset),
  216. trunc(vertexArray[NP + 3].Z + fZOffset));
  217. if vertexArray[NP + 3].Y < yMin then
  218. yMin := vertexArray[NP + 3].Y;
  219. if vertexArray[NP + 3].Y > yMax then
  220. yMax := vertexArray[NP + 3].Y;
  221. NP := NP + 4;
  222. u := u + 1;
  223. end;
  224. v := v + 1;
  225. end;
  226. if yMax - yMin > 0 then
  227. fScalling := self.Height / (yMax - yMin)
  228. else
  229. fScalling := 1;
  230. var
  231. heightToColor := 255 / (abs(yMin) + abs(yMax)) / 255;
  232. var
  233. i := 0;
  234. NP := 0;
  235. while i < Length(vertexArray) - 3 do
  236. begin
  237. with Data do
  238. begin
  239. with VertexBuffer do
  240. begin
  241. Vertices[NP + 0] := vertexArray[i + 0];
  242. Vertices[NP + 1] := vertexArray[i + 1];
  243. Vertices[NP + 2] := vertexArray[i + 2];
  244. Vertices[NP + 3] := vertexArray[i + 3];
  245. if UseRamp then
  246. begin
  247. TexCoord0[NP + 0] := PointF((vertexArray[i + 0].Y + abs(yMin)) *
  248. heightToColor, 0);
  249. TexCoord0[NP + 1] := PointF((vertexArray[i + 1].Y + abs(yMin)) *
  250. heightToColor, 0);
  251. TexCoord0[NP + 2] := PointF((vertexArray[i + 2].Y + abs(yMin)) *
  252. heightToColor, 0);
  253. TexCoord0[NP + 3] := PointF((vertexArray[i + 3].Y + abs(yMin)) *
  254. heightToColor, 0);
  255. end
  256. else
  257. begin
  258. TexCoord0[NP + 0] := PointF((vertexArray[i + 0].X) / SubdivX,
  259. (vertexArray[i + 0].Z) / SubdivZ);
  260. TexCoord0[NP + 1] := PointF((vertexArray[i + 1].X) / SubdivX,
  261. (vertexArray[i + 1].Z) / SubdivZ);
  262. TexCoord0[NP + 2] := PointF((vertexArray[i + 2].X) / SubdivX,
  263. (vertexArray[i + 2].Z) / SubdivZ);
  264. TexCoord0[NP + 3] := PointF((vertexArray[i + 3].X) / SubdivX,
  265. (vertexArray[i + 3].Z) / SubdivZ);
  266. end;
  267. end;
  268. IndexBuffer[NI + 0] := NP + 0;
  269. IndexBuffer[NI + 1] := NP + 1;
  270. IndexBuffer[NI + 2] := NP + 3;
  271. IndexBuffer[NI + 3] := NP + 3;
  272. IndexBuffer[NI + 4] := NP + 1;
  273. IndexBuffer[NI + 5] := NP + 2;
  274. end;
  275. NP := NP + 4;
  276. NI := NI + 6;
  277. inc(i, 4);
  278. end;
  279. Data.CalcTangentBinormals;
  280. finally
  281. end;
  282. end;
  283. procedure TGBETerrain.SetUseRamp(const Value: boolean);
  284. begin
  285. if Value <> FUseRamp then
  286. FUseRamp := Value;
  287. end;
  288. function TGBETerrain.GetHeight(P: TPoint3d): single;
  289. begin
  290. result := CalculateHeight(self, P, fScalling, fSubdivX, fSubdivZ);
  291. end;
  292. //---------------------------------------------------------------------------
  293. procedure Register;
  294. begin
  295. RegisterComponents('GXScene GBE', [TGBETerrain]);
  296. end;
  297. end.