GXS.TerrainRenderer.pas 33 KB

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