GLTerrainRenderer.pas 34 KB

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