GLS.TerrainRenderer.pas 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062
  1. //
  2. // The graphics engine GLScene
  3. //
  4. unit GLS.TerrainRenderer;
  5. (*
  6. Brute-force terrain renderer.
  7. NOTA : multi-materials terrain support is not yet optimized to minimize
  8. texture switches (in case of resued tile textures).
  9. *)
  10. interface
  11. {$I Stage.Defines.inc}
  12. uses
  13. Winapi.OpenGL,
  14. System.Classes,
  15. System.SysUtils,
  16. System.Types,
  17. Stage.OpenGLTokens,
  18. GLS.Scene,
  19. GLS.Coordinates,
  20. GLS.HeightData,
  21. GLS.Material,
  22. Stage.VectorGeometry,
  23. GLS.Context,
  24. GLS.ROAMPatch,
  25. GLS.VectorLists,
  26. GLS.RenderContextInfo,
  27. GLS.XOpenGL,
  28. Stage.Utils,
  29. Stage.VectorTypes,
  30. GLS.Texture;
  31. const
  32. cTilesHashSize = 255;
  33. type
  34. TGetTerrainBoundsEvent = procedure(var l, t, r, b: Single) of object;
  35. TPatchPostRenderEvent = procedure(var rci: TGLRenderContextInfo; const patches: TList) of object;
  36. TGLHeightDataPostRenderEvent = procedure(var rci: TGLRenderContextInfo;
  37. var HeightDatas: TList) of object;
  38. TMaxCLODTrianglesReachedEvent = procedure(var rci: TGLRenderContextInfo)
  39. of object;
  40. TTerrainHighResStyle = (hrsFullGeometry, hrsTesselated);
  41. TTerrainOcclusionTesselate = (totTesselateAlways, totTesselateIfVisible);
  42. TTileManagementFlag = (tmClearUsedFlags, tmMarkUsedTiles,
  43. tmReleaseUnusedTiles, tmAllocateNewTiles, tmWaitForPreparing);
  44. TTileManagementFlags = set of TTileManagementFlag;
  45. (* Basic terrain renderer.
  46. This renderer uses no sophisticated meshing, it just builds and maintains
  47. a set of terrain tiles, performs basic visibility culling and renders its
  48. stuff. You can use it has a base class/sample for more specialized
  49. terrain renderers.
  50. The Terrain heightdata is retrieved directly from a TGLHeightDataSource, and
  51. expressed as z=f(x, y) data. *)
  52. TGLTerrainRenderer = class(TGLSceneObject)
  53. private
  54. FHeightDataSource: TGLHeightDataSource;
  55. FTileSize: Integer;
  56. FQualityDistance, FinvTileSize: Single;
  57. FLastTriangleCount: Integer;
  58. FTilesPerTexture: Single;
  59. FMaxCLODTriangles, FCLODPrecision: Integer;
  60. FBufferVertices: TGLAffineVectorList;
  61. FBufferTexPoints: TGLTexPointList;
  62. FBufferVertexIndices: TGLIntegerList;
  63. FMaterialLibrary: TGLMaterialLibrary;
  64. FOnGetTerrainBounds: TGetTerrainBoundsEvent;
  65. FOnPatchPostRender: TPatchPostRenderEvent;
  66. FOnHeightDataPostRender: TGLHeightDataPostRenderEvent;
  67. FOnMaxCLODTrianglesReached: TMaxCLODTrianglesReachedEvent;
  68. FQualityStyle: TTerrainHighResStyle;
  69. FOcclusionFrameSkip: Integer;
  70. FOcclusionTesselate: TTerrainOcclusionTesselate;
  71. FContourInterval: Integer;
  72. FContourWidth: Integer;
  73. protected
  74. FTilesHash: packed array [0 .. cTilesHashSize] of TList;
  75. procedure MarkAllTilesAsUnused;
  76. procedure ReleaseAllUnusedTiles;
  77. procedure MarkHashedTileAsUsed(const tilePos: TAffineVector);
  78. function HashedTile(const tilePos: TAffineVector; canAllocate: Boolean = True)
  79. : TGLHeightData; overload;
  80. function HashedTile(const xLeft, yTop: Integer; canAllocate: Boolean = True)
  81. : TGLHeightData; overload;
  82. procedure SetHeightDataSource(const val: TGLHeightDataSource);
  83. procedure SetTileSize(const val: Integer);
  84. procedure SetTilesPerTexture(const val: Single);
  85. procedure SetCLODPrecision(const val: Integer);
  86. procedure SetMaterialLibrary(const Val: TGLMaterialLibrary);
  87. procedure SetQualityStyle(const Val: TTerrainHighResStyle);
  88. procedure SetOcclusionFrameSkip(Val: Integer);
  89. procedure Notification(AComponent: TComponent; Operation: TOperation); override;
  90. procedure DestroyHandle; override;
  91. procedure ReleaseAllTiles;
  92. procedure OnTileDestroyed(Sender: TObject);
  93. function GetPreparedPatch(const TilePos, EyePos: TAffineVector;
  94. TexFactor: Single; HDList: TList): TGLROAMPatch;
  95. public
  96. (* TileManagement flags can be used to turn off various Tile cache management features.
  97. This helps to prevent unnecessary tile cache flushes, when rendering from multiple cameras *)
  98. TileManagement: TTileManagementFlags;
  99. constructor Create(AOwner: TComponent); override;
  100. destructor Destroy; override;
  101. procedure BuildList(var rci: TGLRenderContextInfo); override;
  102. function RayCastIntersect(const rayStart, rayVector: TGLVector;
  103. intersectPoint: PGLVector = nil; intersectNormal: PGLVector = nil): Boolean; override;
  104. (* Interpolates height for the given point.
  105. Expects a point expressed in absolute coordinates. *)
  106. function InterpolatedHeight(const p: TGLVector): Single; overload;
  107. function InterpolatedHeight(const p: TAffineVector): Single; overload;
  108. // Triangle count for the last render
  109. property LastTriangleCount: Integer read FLastTriangleCount;
  110. function HashedTileCount: Integer;
  111. published
  112. // Specifies the HeightData provider component.
  113. property HeightDataSource: TGLHeightDataSource read FHeightDataSource
  114. write SetHeightDataSource;
  115. // Size of the terrain tiles. Must be a power of two.
  116. property TileSize: Integer read FTileSize write SetTileSize default 16;
  117. // Number of tiles required for a full texture map.
  118. property TilesPerTexture: Single read FTilesPerTexture write SetTilesPerTexture;
  119. (* Link to the material library holding terrain materials.
  120. If unspecified, and for all terrain tiles with unspecified material,
  121. the terrain renderer's material is used. *)
  122. property MaterialLibrary: TGLMaterialLibrary read FMaterialLibrary write SetMaterialLibrary;
  123. (* Quality distance hint.
  124. This parameter gives an hint to the terrain renderer at which distance
  125. the terrain quality can be degraded to favor speed. The distance is
  126. expressed in absolute coordinates units.
  127. All tiles closer than this distance are rendered according to
  128. QualityStyle and with a static resolution. *)
  129. property QualityDistance: Single read FQualityDistance write FQualityDistance;
  130. (* Determines how high-res tiles (closer than QualityDistance) are rendered.
  131. hrsFullGeometry (default value) means that the high-res tiles are rendered
  132. with full-geometry, and no LOD of any kind, while hrsTesselated means
  133. the tiles will be tesselated once, with the best output for the
  134. CLODPrecision, and the result of that tesselation will be reused
  135. in further frames without any adpative tesselation. *)
  136. property QualityStyle: TTerrainHighResStyle read FQualityStyle
  137. write SetQualityStyle default hrsFullGeometry;
  138. (* Maximum number of CLOD triangles per scene.
  139. Triangles in high-resolution tiles (closer than QualityDistance) do
  140. not count toward this limit. *)
  141. property MaxCLODTriangles: Integer read FMaxCLODTriangles
  142. write FMaxCLODTriangles default 65536;
  143. (* Precision of CLOD tiles.
  144. The lower the value, the higher the precision and triangle count.
  145. Large values will result in coarse terrain.
  146. high-resolution tiles (closer than QualityDistance) ignore this setting. *)
  147. property CLODPrecision: Integer read FCLODPrecision write SetCLODPrecision default 100;
  148. (* Numbers of frames to skip for a tile when occlusion testing found it invisible.
  149. Occlusion testing can help reduce CPU, T&L and fillrate requirements
  150. when tiles are occluded, either by the terrain itself (tiles behind
  151. a mountain or a cliff) or by geometry that was rendered before the
  152. terrain (large buildings). If there is little occlusion in your scene
  153. (such as in top down or high-altitude view), turning occlusion on
  154. may have a slightly negative effect on framerate.
  155. It works by turning off rendering of tiles for the specified number
  156. of frames if it has been found invisible, after FrameSkip number
  157. of frames have been skipped, it will be rendered again, and a new
  158. occlusion testing made. This makes occlusion-testing a frame-to-frame
  159. coherency optimization, and as such, shouldn't be used for static
  160. rendering (ie. leave value to its default of zero).
  161. This optimization requires the hardware to support GL_NV_occlusion_query *)
  162. property OcclusionFrameSkip: Integer read FOcclusionFrameSkip
  163. write SetOcclusionFrameSkip default 0;
  164. (* Determines if and how occlusion testing affects tesselation.
  165. Turning off tesselation of tiles determined invisible can improve
  166. performance, however, it may result in glitches since the tesselation
  167. of an invisible tile can have a slight effect on the tesselation
  168. of its adjacent tiles (by forcing higher resolution at the border
  169. for instance). This negative effect can be lessened by increasing
  170. the QualityDistance, so that glitches will appear farther away
  171. (this will mean increasing your triangle count though, so you'll
  172. trade CPU power against T&L power). *)
  173. property OcclusionTesselate: TTerrainOcclusionTesselate
  174. read FOcclusionTesselate write FOcclusionTesselate default totTesselateIfVisible;
  175. (* Allows to specify terrain bounds.
  176. Default rendering bounds will reach depth of view in all direction,
  177. with this event you can chose to specify a smaller rendered terrain area *)
  178. property OnGetTerrainBounds: TGetTerrainBoundsEvent read FOnGetTerrainBounds
  179. write FOnGetTerrainBounds;
  180. (* Invoked for each rendered patch after terrain render has completed.
  181. The list holds TGLROAMPatch objects and allows per-patch
  182. post-processings, like waters, trees... It is invoked *before*
  183. OnHeightDataPostRender. *)
  184. property OnPatchPostRender: TPatchPostRenderEvent read FOnPatchPostRender
  185. write FOnPatchPostRender;
  186. (* Invoked for each heightData not culled out by the terrain renderer.
  187. The list holds TGLHeightData objects and allows per-patch
  188. post-processings, like waters, trees... It is invoked *after*
  189. OnPatchPostRender. *)
  190. property OnHeightDataPostRender: TGLHeightDataPostRenderEvent
  191. read FOnHeightDataPostRender write FOnHeightDataPostRender;
  192. (* Invoked whenever the MaxCLODTriangles limit was reached during last rendering.
  193. This forced the terrain renderer to resize the buffer, which affects performance.
  194. If this event is fired frequently, one should increase MaxCLODTriangles. *)
  195. property OnMaxCLODTrianglesReached: TMaxCLODTrianglesReachedEvent
  196. read FOnMaxCLODTrianglesReached write FOnMaxCLODTrianglesReached;
  197. // Distance between contours - zero (default) for no contours PGS
  198. property ContourInterval: Integer read FContourInterval
  199. write FContourInterval default 0;
  200. // Width of Isolines
  201. property ContourWidth: Integer read FContourWidth
  202. write FContourWidth default 1;
  203. end;
  204. // ------------------------------------------------------------------
  205. implementation
  206. // ------------------------------------------------------------------
  207. function HashKey(const XLeft, YTop: Integer): Integer;
  208. begin
  209. Result := (XLeft + (XLeft shr 8) + (XLeft shr 16) + (YTop shl 1) +
  210. (YTop shr 9) + (YTop shr 17)) and cTilesHashSize;
  211. end;
  212. // ------------------
  213. // ------------------ TGLTerrainRenderer ------------------
  214. // ------------------
  215. constructor TGLTerrainRenderer.Create(AOwner: TComponent);
  216. var
  217. i: Integer;
  218. begin
  219. inherited Create(AOwner);
  220. for i := 0 to cTilesHashSize do
  221. FTilesHash[i] := TList.Create;
  222. ObjectStyle := ObjectStyle + [osDirectDraw];
  223. FTileSize := 16;
  224. FinvTileSize := 1 / 16;
  225. FTilesPerTexture := 1;
  226. FMaxCLODTriangles := 65536;
  227. FCLODPrecision := 100;
  228. FOcclusionTesselate := totTesselateIfVisible;
  229. FBufferVertices := TGLAffineVectorList.Create;
  230. FBufferTexPoints := TGLTexPointList.Create;
  231. FBufferVertexIndices := TGLIntegerList.Create;
  232. TileManagement := [tmClearUsedFlags, tmMarkUsedTiles, tmReleaseUnusedTiles,
  233. tmAllocateNewTiles];
  234. end;
  235. destructor TGLTerrainRenderer.Destroy;
  236. var
  237. i: Integer;
  238. begin
  239. FBufferVertices.Free;
  240. FBufferTexPoints.Free;
  241. FBufferVertexIndices.Free;
  242. ReleaseAllTiles;
  243. for i := 0 to cTilesHashSize do
  244. begin
  245. FTilesHash[i].Free;
  246. FTilesHash[i] := nil;
  247. end;
  248. inherited Destroy;
  249. end;
  250. procedure TGLTerrainRenderer.Notification(AComponent: TComponent;
  251. Operation: TOperation);
  252. begin
  253. if Operation = opRemove then
  254. begin
  255. if AComponent = FHeightDataSource then
  256. HeightDataSource := nil
  257. else if AComponent = FMaterialLibrary then
  258. MaterialLibrary := nil;
  259. end;
  260. inherited;
  261. end;
  262. procedure TGLTerrainRenderer.DestroyHandle;
  263. begin
  264. inherited;
  265. ReleaseAllTiles;
  266. if Assigned(HeightDataSource) then
  267. HeightDataSource.Clear;
  268. end;
  269. function TGLTerrainRenderer.RayCastIntersect(const rayStart, rayVector: TGLVector;
  270. intersectPoint: PGLVector = nil; intersectNormal: PGLVector = nil): Boolean;
  271. var
  272. p1, d, p2, p3: TGLVector;
  273. step, i, h, minH, maxH, p1height: Single;
  274. startedAbove: Boolean;
  275. failSafe: Integer;
  276. AbsX, AbsY, AbsZ: TGLVector;
  277. begin
  278. Result := False;
  279. if Assigned(HeightDataSource) then
  280. begin
  281. step := (Scale.X + Scale.Y); // Initial step size guess
  282. i := step;
  283. d := VectorNormalize(rayVector);
  284. AbsZ := VectorNormalize(LocalToAbsolute(ZHMGVector));
  285. startedAbove := ((InterpolatedHeight(rayStart) - VectorDotProduct(rayStart,
  286. AbsZ)) < 0);
  287. maxH := Scale.Z * 256;
  288. minH := -Scale.Z * 256;
  289. failSafe := 0;
  290. while True do
  291. begin
  292. p1 := VectorCombine(rayStart, d, 1, i);
  293. h := InterpolatedHeight(p1);
  294. p1height := VectorDotProduct(AbsZ, p1);
  295. if Abs(h - p1height) < 0.1 then
  296. begin // Need a tolerance variable here (how close is good enough?)
  297. Result := True;
  298. Break;
  299. end
  300. else
  301. begin
  302. if startedAbove then
  303. begin
  304. if h < p1height then
  305. i := i + step;
  306. if (h - p1height) > 0 then
  307. begin
  308. step := step * 0.5;
  309. i := i - step;
  310. end;
  311. end
  312. else
  313. begin
  314. if h > p1height then
  315. i := i + step;
  316. end;
  317. end;
  318. Inc(failSafe);
  319. if failSafe > 1024 then
  320. Break;
  321. if VectorDotProduct(AbsZ, d) < 0 then
  322. begin
  323. if h < minH then
  324. Exit;
  325. end
  326. else if h > maxH then
  327. Exit;
  328. end;
  329. if Result then
  330. begin
  331. p1 := VectorAdd(p1, VectorScale(AbsZ, InterpolatedHeight(p1) -
  332. VectorDotProduct(p1, AbsZ)));
  333. if Assigned(intersectPoint) then
  334. intersectPoint^ := p1;
  335. // Calc Normal
  336. if Assigned(intersectNormal) then
  337. begin
  338. // Get 2 nearby points for cross-product
  339. AbsX := VectorNormalize(LocalToAbsolute(XHMGVector));
  340. AbsY := VectorNormalize(LocalToAbsolute(YHMGVector));
  341. p2 := VectorAdd(p1, VectorScale(AbsX, 0.1));
  342. p2 := VectorAdd(p2, VectorScale(AbsZ, InterpolatedHeight(p2) -
  343. VectorDotProduct(p2, AbsZ)));
  344. p3 := VectorAdd(p1, VectorScale(AbsY, 0.1));
  345. p3 := VectorAdd(p3, VectorScale(AbsZ, InterpolatedHeight(p3) -
  346. VectorDotProduct(p3, AbsZ)));
  347. intersectNormal^ :=
  348. VectorNormalize(VectorCrossProduct(VectorSubtract(p1, p2),
  349. VectorSubtract(p3, p1)));
  350. end;
  351. end;
  352. end;
  353. end;
  354. procedure TGLTerrainRenderer.ReleaseAllTiles;
  355. var
  356. i, k: Integer;
  357. hd: TGLHeightData;
  358. begin
  359. for i := 0 to cTilesHashSize do
  360. with FTilesHash[i] do
  361. begin
  362. for k := Count - 1 downto 0 do
  363. begin
  364. hd := TGLHeightData(Items[k]);
  365. OnTileDestroyed(hd);
  366. hd.OnDestroy := nil;
  367. hd.Release;
  368. end;
  369. Clear;
  370. end;
  371. end;
  372. procedure TGLTerrainRenderer.OnTileDestroyed(Sender: TObject);
  373. var
  374. List: TList;
  375. begin
  376. with Sender as TGLHeightData do
  377. begin
  378. if ObjectTag <> nil then
  379. begin
  380. ObjectTag.Free;
  381. ObjectTag := nil;
  382. end;
  383. List := FTilesHash[HashKey(xLeft, yTop)];
  384. Assert(Assigned(List));
  385. List.Remove(Sender);
  386. end;
  387. end;
  388. function TGLTerrainRenderer.InterpolatedHeight(const p: TGLVector): Single;
  389. var
  390. pLocal: TGLVector;
  391. begin
  392. if Assigned(HeightDataSource) then
  393. begin
  394. pLocal := AbsoluteToLocal(p);
  395. Result := HeightDataSource.InterpolatedHeight(pLocal.X, pLocal.Y,
  396. TileSize + 1) * Scale.Z * (1 / 128);
  397. end
  398. else
  399. Result := 0;
  400. end;
  401. function TGLTerrainRenderer.InterpolatedHeight(const p: TAffineVector): Single;
  402. begin
  403. Result := InterpolatedHeight(PointMake(p));
  404. end;
  405. procedure TGLTerrainRenderer.BuildList(var rci: TGLRenderContextInfo);
  406. var
  407. vEye, vEyeDirection: TGLVector;
  408. TilePos, AbsTilePos, Observer: TAffineVector;
  409. DeltaX, nbX, iX: Integer;
  410. DeltaY, nbY, iY: Integer;
  411. n, rpIdxDelta, AccumCount: Integer;
  412. f, TileRadius, TileGroundRadius, TexFactor, TileDist, qDist: Single;
  413. Patch, PrevPatch: TGLROAMPatch;
  414. PatchList, RowList, prevRow, buf: TList;
  415. PostRenderPatchList, postRenderHeightDataList: TList;
  416. rcci: TGLRenderContextClippingInfo;
  417. CurrentMaterialName: String;
  418. MaxTilePosX, MaxTilePosY, MinTilePosX, MinTilePosY: Single;
  419. t_l, t_t, t_r, t_b: Single;
  420. procedure ApplyMaterial(const materialName: String);
  421. begin
  422. if (MaterialLibrary = nil) or (currentMaterialName = materialName) then
  423. Exit;
  424. // flush whatever is in progress
  425. TGLROAMPatch.FlushAccum(FBufferVertices, FBufferVertexIndices,
  426. FBufferTexPoints);
  427. // unapply current
  428. if currentMaterialName = '' then
  429. begin
  430. repeat
  431. // ... proper multipass support will be implemented later
  432. until not Material.UnApply(rci);
  433. end
  434. else
  435. begin
  436. repeat
  437. // ... proper multipass support will be implemented later
  438. until not MaterialLibrary.UnApplyMaterial(rci);
  439. end;
  440. // apply new
  441. if materialName = '' then
  442. Material.Apply(rci)
  443. else
  444. MaterialLibrary.ApplyMaterial(materialName, rci);
  445. CurrentMaterialName := MaterialName;
  446. end;
  447. begin
  448. if csDesigning in ComponentState then
  449. Exit;
  450. if HeightDataSource = nil then
  451. Exit;
  452. currentMaterialName := '';
  453. // first project eye position into heightdata coordinates
  454. vEye := VectorTransform(rci.cameraPosition, InvAbsoluteMatrix);
  455. vEyeDirection := VectorTransform(rci.cameraDirection, InvAbsoluteMatrix);
  456. SetVector(observer, vEye);
  457. vEye.X := Round(vEye.X * FinvTileSize - 0.5) * TileSize +
  458. TileSize * 0.5;
  459. vEye.Y := Round(vEye.Y * FinvTileSize - 0.5) * TileSize +
  460. TileSize * 0.5;
  461. tileGroundRadius := Sqr(TileSize * 0.5 * Scale.X) +
  462. Sqr(TileSize * 0.5 * Scale.Y);
  463. tileRadius := Sqrt(tileGroundRadius + Sqr(256 * Scale.Z));
  464. tileGroundRadius := Sqrt(tileGroundRadius);
  465. // now, we render a quad grid centered on eye position
  466. SetVector(tilePos, vEye);
  467. tilePos.Z := 0;
  468. f := (rci.rcci.farClippingDistance + tileGroundRadius) / Scale.X;
  469. f := Round(f * FinvTileSize + 1.0) * TileSize;
  470. maxTilePosX := vEye.X + f;
  471. maxTilePosY := vEye.Y + f;
  472. minTilePosX := vEye.X - f;
  473. minTilePosY := vEye.Y - f;
  474. if Assigned(FOnGetTerrainBounds) then
  475. begin
  476. // User-specified terrain bounds, may override ours
  477. t_l := minTilePosX;
  478. t_t := maxTilePosY;
  479. t_r := maxTilePosX;
  480. t_b := minTilePosY;
  481. FOnGetTerrainBounds(t_l, t_t, t_r, t_b);
  482. t_l := Round(t_l / TileSize - 0.5) * TileSize + TileSize * 0.5;
  483. t_t := Round(t_t / TileSize - 0.5) * TileSize - TileSize * 0.5;
  484. t_r := Round(t_r / TileSize - 0.5) * TileSize - TileSize * 0.5;
  485. t_b := Round(t_b / TileSize - 0.5) * TileSize + TileSize * 0.5;
  486. if maxTilePosX > t_r then
  487. maxTilePosX := t_r;
  488. if maxTilePosY > t_t then
  489. maxTilePosY := t_t;
  490. if minTilePosX < t_l then
  491. minTilePosX := t_l;
  492. if minTilePosY < t_b then
  493. minTilePosY := t_b;
  494. end;
  495. // if max is less than min, we have nothing to render
  496. if (maxTilePosX < minTilePosX) or (maxTilePosY < minTilePosY) then
  497. Exit;
  498. nbX := Round((maxTilePosX - minTilePosX) / TileSize);
  499. nbY := Round((maxTilePosY - minTilePosY) / TileSize);
  500. texFactor := 1 / (TilesPerTexture * TileSize);
  501. rcci := rci.rcci;
  502. if QualityDistance > 0 then
  503. qDist := QualityDistance + tileRadius * 0.5
  504. else
  505. qDist := -1;
  506. SetROAMTrianglesCapacity(MaxCLODTriangles);
  507. n := MaxInteger(MaxCLODTriangles * 2, Integer(Sqr(TileSize + 1) * 2));
  508. FBufferVertices.Capacity := n;
  509. FBufferTexPoints.Capacity := n;
  510. xgl.PushState;
  511. try
  512. if GL.ARB_multitexture then
  513. xgl.MapTexCoordToDual
  514. else
  515. xgl.MapTexCoordToMain;
  516. gl.PushMatrix;
  517. gl.Scalef(1, 1, 1 / 128);
  518. gl.Translatef(-0.5 * TileSize, -0.5 * TileSize, 0);
  519. gl.EnableClientState(GL_VERTEX_ARRAY);
  520. xgl.EnableClientState(GL_TEXTURE_COORD_ARRAY);
  521. gl.DisableClientState(GL_COLOR_ARRAY);
  522. gl.DisableClientState(GL_NORMAL_ARRAY);
  523. gl.VertexPointer(3, GL_FLOAT, 0, FBufferVertices.list);
  524. xgl.TexCoordPointer(2, GL_FLOAT, 0, FBufferTexPoints.list);
  525. finally
  526. xgl.PopState;
  527. end;
  528. HeightDataSource.Data.LockList; // Lock out the HDS thread while rendering
  529. FLastTriangleCount := 0;
  530. patchList := TList.Create;
  531. patchList.Capacity := (nbX + 1) * (nbY + 1);
  532. rowList := TList.Create;
  533. prevRow := TList.Create;
  534. if Assigned(FOnPatchPostRender) then
  535. postRenderPatchList := TList.Create
  536. else
  537. postRenderPatchList := nil;
  538. if Assigned(FOnHeightDataPostRender) then
  539. postRenderHeightDataList := TList.Create
  540. else
  541. postRenderHeightDataList := nil;
  542. MarkAllTilesAsUnused;
  543. AbsoluteMatrix; // makes sure it is available
  544. // determine orientation (to render front-to-back)
  545. if vEyeDirection.X >= 0 then
  546. deltaX := TileSize
  547. else
  548. begin
  549. deltaX := -TileSize;
  550. minTilePosX := maxTilePosX;
  551. end;
  552. if vEyeDirection.Y >= 0 then
  553. deltaY := TileSize
  554. else
  555. begin
  556. deltaY := -TileSize;
  557. minTilePosY := maxTilePosY;
  558. end;
  559. TileRadius := TileRadius;
  560. TilePos.Y := minTilePosY;
  561. for iY := 0 to nbY - 1 do
  562. begin
  563. tilePos.X := minTilePosX;
  564. prevPatch := nil;
  565. n := 0;
  566. for iX := 0 to nbX do
  567. begin
  568. absTilePos := VectorTransform(tilePos, DirectAbsoluteMatrix^);
  569. if not IsVolumeClipped(absTilePos, tileRadius, rcci.frustum) then
  570. begin
  571. patch := GetPreparedPatch(tilePos, observer, texFactor,
  572. postRenderHeightDataList);
  573. if patch <> nil then
  574. begin
  575. tileDist := VectorDistance(PAffineVector(@rcci.origin)^, absTilePos);
  576. patch.HighRes := (tileDist < qDist);
  577. if not patch.HighRes then
  578. patch.ResetTessellation;
  579. if Assigned(prevPatch) then
  580. begin
  581. if deltaX > 0 then
  582. patch.ConnectToTheWest(prevPatch)
  583. else
  584. prevPatch.ConnectToTheWest(patch);
  585. end;
  586. if (prevRow.Count > n) and (prevRow.Items[n] <> nil) then
  587. begin
  588. if deltaY > 0 then
  589. patch.ConnectToTheNorth(TGLROAMPatch(prevRow.Items[n]))
  590. else
  591. TGLROAMPatch(prevRow.Items[n]).ConnectToTheNorth(patch);
  592. end;
  593. if patch.HighRes then
  594. begin
  595. // high-res patches are issued immediately
  596. ApplyMaterial(patch.HeightData.materialName);
  597. patch.RenderHighRes(FBufferVertices, FBufferVertexIndices,
  598. FBufferTexPoints, (QualityStyle = hrsTesselated));
  599. FLastTriangleCount := FLastTriangleCount + patch.TriangleCount;
  600. end
  601. else
  602. begin
  603. // CLOD patches are issued after tesselation
  604. patchList.Add(patch);
  605. end;
  606. prevPatch := patch;
  607. rowList.Add(patch);
  608. if Assigned(postRenderPatchList) then
  609. postRenderPatchList.Add(patch);
  610. end
  611. else
  612. begin
  613. prevPatch := nil;
  614. rowList.Add(nil);
  615. end;
  616. end
  617. else
  618. begin
  619. MarkHashedTileAsUsed(tilePos);
  620. prevPatch := nil;
  621. rowList.Add(nil);
  622. end;
  623. tilePos.X := tilePos.X + deltaX;
  624. Inc(n);
  625. end;
  626. tilePos.Y := tilePos.Y + deltaY;
  627. buf := prevRow;
  628. prevRow := rowList;
  629. rowList := buf;
  630. rowList.Count := 0;
  631. end;
  632. accumCount := FBufferVertexIndices.Capacity shr 3;
  633. // Interleave Tesselate and Render so we can send some work to the hardware
  634. // while the CPU keeps working
  635. rpIdxDelta := Round(2 * f / TileSize) + 2;
  636. for n := 0 to patchList.Count - 1 + rpIdxDelta do
  637. begin
  638. if n < patchList.Count then
  639. begin
  640. patch := TGLROAMPatch(patchList[n]);
  641. if Assigned(patch) then
  642. begin
  643. if (patch.LastOcclusionTestPassed) or (patch.OcclusionCounter <= 0) or
  644. (OcclusionTesselate = totTesselateAlways) then
  645. patch.SafeTesselate;
  646. end;
  647. end;
  648. if n >= rpIdxDelta then
  649. begin
  650. patch := TGLROAMPatch(patchList[n - rpIdxDelta]);
  651. if Assigned(patch) then
  652. begin
  653. ApplyMaterial(patch.HeightData.materialName);
  654. patch.RenderAccum(FBufferVertices, FBufferVertexIndices,
  655. FBufferTexPoints, accumCount);
  656. Inc(FLastTriangleCount, patch.TriangleCount);
  657. end;
  658. end;
  659. end;
  660. if (GetROAMTrianglesCapacity > MaxCLODTriangles) and
  661. Assigned(FOnMaxCLODTrianglesReached) then
  662. begin
  663. FOnMaxCLODTrianglesReached(rci);
  664. // Fire an event if the MaxCLODTriangles limit was reached
  665. end;
  666. TGLROAMPatch.FlushAccum(FBufferVertices, FBufferVertexIndices,
  667. FBufferTexPoints);
  668. xgl.PushState;
  669. try
  670. if gl.ARB_multitexture then
  671. xgl.MapTexCoordToDual
  672. else
  673. xgl.MapTexCoordToMain;
  674. gl.DisableClientState(GL_VERTEX_ARRAY);
  675. xgl.DisableClientState(GL_TEXTURE_COORD_ARRAY);
  676. finally
  677. xgl.PopState;
  678. end;
  679. ApplyMaterial('');
  680. if Assigned(postRenderPatchList) then
  681. begin
  682. FOnPatchPostRender(rci, postRenderPatchList);
  683. postRenderPatchList.Free;
  684. end;
  685. if Assigned(postRenderHeightDataList) then
  686. begin
  687. FOnHeightDataPostRender(rci, postRenderHeightDataList);
  688. postRenderHeightDataList.Free;
  689. end;
  690. gl.PopMatrix;
  691. if (tmReleaseUnusedTiles in TileManagement) then
  692. begin // Tile cache management option
  693. ReleaseAllUnusedTiles;
  694. HeightDataSource.CleanUp;
  695. end;
  696. rowList.Free;
  697. prevRow.Free;
  698. patchList.Free;
  699. HeightDataSource.Data.UnLockList;
  700. end;
  701. procedure TGLTerrainRenderer.MarkAllTilesAsUnused;
  702. var
  703. i, j, zero: Integer;
  704. begin
  705. if not(tmClearUsedFlags in TileManagement) then
  706. Exit; // Tile cache management option
  707. for i := 0 to cTilesHashSize do
  708. with FTilesHash[i] do
  709. begin
  710. zero := 0;
  711. for j := Count - 1 downto 0 do
  712. TGLHeightData(Items[j]).Tag := zero;
  713. end;
  714. end;
  715. procedure TGLTerrainRenderer.ReleaseAllUnusedTiles;
  716. var
  717. i, j: Integer;
  718. hashList: TList;
  719. hd: TGLHeightData;
  720. begin
  721. for i := 0 to cTilesHashSize do
  722. begin
  723. hashList := FTilesHash[i];
  724. for j := hashList.Count - 1 downto 0 do
  725. begin
  726. hd := TGLHeightData(hashList.Items[j]);
  727. if hd.Tag = 0 then
  728. begin
  729. hashList.Delete(j);
  730. OnTileDestroyed(hd);
  731. hd.OnDestroy := nil;
  732. hd.Release;
  733. end;
  734. end;
  735. end;
  736. end;
  737. function TGLTerrainRenderer.HashedTileCount: Integer;
  738. var
  739. i: Integer;
  740. hashList: TList;
  741. cnt: Integer;
  742. begin
  743. cnt := 0;
  744. for i := 0 to cTilesHashSize do
  745. begin
  746. hashList := FTilesHash[i]; // get the number of tiles in each list
  747. cnt := cnt + hashList.Count; // Add the current list's count to the total
  748. end;
  749. Result := cnt;
  750. end;
  751. procedure TGLTerrainRenderer.MarkHashedTileAsUsed(const tilePos: TAffineVector);
  752. var
  753. hd: TGLHeightData;
  754. canAllocate: Boolean;
  755. begin
  756. if not(tmMarkUsedTiles in TileManagement) then
  757. Exit; // Mark used tiles option
  758. canAllocate := tmAllocateNewTiles in TileManagement;
  759. // Allocate tile if not in the list
  760. hd := HashedTile(tilePos, canAllocate);
  761. if Assigned(hd) then
  762. hd.Tag := 1;
  763. end;
  764. function TGLTerrainRenderer.HashedTile(const tilePos: TAffineVector;
  765. canAllocate: Boolean = True): TGLHeightData;
  766. var
  767. XLeft, YTop: Integer;
  768. begin
  769. XLeft := Round(tilePos.X * FinvTileSize - 0.5) * (TileSize);
  770. YTop := Round(tilePos.Y * FinvTileSize - 0.5) * (TileSize);
  771. Result := HashedTile(xLeft, yTop, canAllocate);
  772. end;
  773. function TGLTerrainRenderer.HashedTile(const xLeft, yTop: Integer;
  774. canAllocate: Boolean = True): TGLHeightData;
  775. var
  776. i: Integer;
  777. hd: TGLHeightData;
  778. hashList: TList;
  779. begin
  780. // is the tile already in our list?
  781. hashList := FTilesHash[HashKey(xLeft, yTop)];
  782. for i := hashList.Count - 1 downto 0 do
  783. begin
  784. hd := TGLHeightData(hashList.Items[i]);
  785. if (hd.xLeft = xLeft) and (hd.yTop = yTop) then
  786. begin
  787. if hd.DontUse then
  788. begin
  789. // This tile has now been replaced. Remove it from the hash-table.
  790. HashList.Remove(hd);
  791. end
  792. else
  793. begin
  794. Result := hd;
  795. Exit;
  796. end;
  797. end;
  798. end;
  799. // if not, request it
  800. if canAllocate then
  801. begin
  802. Result := HeightDataSource.GetData(xLeft, yTop, TileSize + 1, hdtSmallInt);
  803. Result.RegisterUse;
  804. Result.OnDestroy := OnTileDestroyed;
  805. if Result.DataState <> hdsNone then
  806. Result.DataType := hdtSmallInt;
  807. FTilesHash[HashKey(xLeft, yTop)].Add(Result);
  808. end
  809. else
  810. Result := nil;
  811. end;
  812. function TGLTerrainRenderer.GetPreparedPatch(const TilePos,
  813. EyePos: TAffineVector; TexFactor: Single; HDList: TList): TGLROAMPatch;
  814. var
  815. Tile: TGLHeightData;
  816. Patch: TGLROAMPatch;
  817. XLeft, YTop: Integer;
  818. CanAllocate: Boolean;
  819. begin
  820. CanAllocate := tmAllocateNewTiles in TileManagement;
  821. XLeft := Round(TilePos.X * FinvTileSize - 0.5) * TileSize;
  822. YTop := Round(TilePos.Y * FinvTileSize - 0.5) * TileSize;
  823. Tile := HashedTile(xLeft, yTop, canAllocate);
  824. Result := nil;
  825. if not Assigned(tile) then
  826. Exit;
  827. if (tmClearUsedFlags in TileManagement) // Tile cache management option
  828. then
  829. Tile.Tag := 1; // mark tile as used
  830. if Assigned(hdList) then
  831. hdList.Add(tile);
  832. // if tile.DataState=hdsNone then begin
  833. if Tile.DataState <> hdsReady then
  834. begin
  835. Result := nil; // if the tile is still not hdsReady, then skip it
  836. end
  837. else
  838. begin
  839. Patch := TGLROAMPatch(Tile.ObjectTag);
  840. if not Assigned(patch) then
  841. begin
  842. // spawn ROAM patch
  843. Patch := TGLROAMPatch.Create;
  844. Patch.ContourInterval := ContourInterval;
  845. Patch.ContourWidth := ContourWidth;
  846. Tile.ObjectTag := patch;
  847. Patch.HeightData := tile;
  848. Patch.VertexScale := XYZVector;
  849. Patch.VertexOffset := tilePos;
  850. Patch.OcclusionSkip := OcclusionFrameSkip;
  851. case Tile.TextureCoordinatesMode of
  852. tcmWorld:
  853. begin
  854. Patch.TextureScale := AffineVectorMake(TexFactor, -TexFactor,
  855. texFactor);
  856. Patch.TextureOffset := AffineVectorMake(XLeft * TexFactor,
  857. 1 - YTop * texFactor, 0);
  858. end;
  859. tcmLocal:
  860. begin
  861. with Tile.TextureCoordinatesScale do
  862. Patch.TextureScale := AffineVectorMake(texFactor * S,
  863. -texFactor * t, texFactor);
  864. with Tile.TextureCoordinatesOffset do
  865. Patch.TextureOffset := AffineVectorMake(0 + S, 1 + t, 0);
  866. end;
  867. else
  868. Assert(False);
  869. end;
  870. Patch.ComputeVariance(FCLODPrecision);
  871. end;
  872. Patch.ObserverPosition := VectorSubtract(eyePos, tilePos);
  873. Result := patch;
  874. end;
  875. end;
  876. procedure TGLTerrainRenderer.SetHeightDataSource(const val: TGLHeightDataSource);
  877. begin
  878. if FHeightDataSource <> val then
  879. begin
  880. if Assigned(FHeightDataSource) then
  881. begin
  882. FHeightDataSource.RemoveFreeNotification(Self);
  883. ReleaseAllTiles;
  884. FHeightDataSource.Clear;
  885. end;
  886. FHeightDataSource := val;
  887. if Assigned(FHeightDataSource) then
  888. FHeightDataSource.FreeNotification(Self);
  889. StructureChanged;
  890. end;
  891. end;
  892. procedure TGLTerrainRenderer.SetTileSize(const val: Integer);
  893. begin
  894. if val <> FTileSize then
  895. begin
  896. if val < 8 then
  897. FTileSize := 8
  898. else
  899. FTileSize := RoundUpToPowerOf2(val);
  900. FinvTileSize := 1 / FTileSize;
  901. ReleaseAllTiles;
  902. StructureChanged;
  903. end;
  904. end;
  905. procedure TGLTerrainRenderer.SetTilesPerTexture(const val: Single);
  906. begin
  907. if val <> FTilesPerTexture then
  908. begin
  909. FTilesPerTexture := val;
  910. StructureChanged;
  911. end;
  912. end;
  913. procedure TGLTerrainRenderer.SetCLODPrecision(const val: Integer);
  914. var
  915. i, k: Integer;
  916. hd: TGLHeightData;
  917. begin
  918. if val <> FCLODPrecision then
  919. begin
  920. FCLODPrecision := val;
  921. if FCLODPrecision < 1 then
  922. FCLODPrecision := 1;
  923. // drop all ROAM data (CLOD has changed, rebuild required)
  924. for i := 0 to cTilesHashSize do
  925. with FTilesHash[i] do
  926. begin
  927. for k := Count - 1 downto 0 do
  928. begin
  929. hd := TGLHeightData(Items[k]);
  930. if Assigned(hd.ObjectTag) then
  931. begin
  932. (hd.ObjectTag as TGLROAMPatch).Free;
  933. hd.ObjectTag := nil;
  934. end;
  935. end;
  936. Clear;
  937. end;
  938. end;
  939. end;
  940. procedure TGLTerrainRenderer.SetMaterialLibrary(const val: TGLMaterialLibrary);
  941. begin
  942. if val <> FMaterialLibrary then
  943. begin
  944. FMaterialLibrary := val;
  945. StructureChanged;
  946. end;
  947. end;
  948. procedure TGLTerrainRenderer.SetQualityStyle(const val: TTerrainHighResStyle);
  949. begin
  950. if val <> FQualityStyle then
  951. begin
  952. FQualityStyle := val;
  953. StructureChanged;
  954. end;
  955. end;
  956. procedure TGLTerrainRenderer.SetOcclusionFrameSkip(val: Integer);
  957. var
  958. i, k: Integer;
  959. hd: TGLHeightData;
  960. begin
  961. if val < 0 then
  962. val := 0;
  963. if FOcclusionFrameSkip <> val then
  964. begin
  965. FOcclusionFrameSkip := val;
  966. for i := 0 to cTilesHashSize do
  967. with FTilesHash[i] do
  968. begin
  969. for k := Count - 1 downto 0 do
  970. begin
  971. hd := TGLHeightData(Items[k]);
  972. if hd.ObjectTag <> nil then
  973. TGLROAMPatch(hd.ObjectTag).OcclusionSkip := OcclusionFrameSkip;
  974. end;
  975. end;
  976. NotifyChange(Self);
  977. end;
  978. end;
  979. // ------------------------------------------------------------------
  980. initialization
  981. // ------------------------------------------------------------------
  982. RegisterClass(TGLTerrainRenderer);
  983. end.