GLS.HeightTileFileHDS.pas 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit GLS.HeightTileFileHDS;
  5. (* HeightDataSource for the HTF (HeightTileFile) format *)
  6. interface
  7. {$I GLScene.inc}
  8. uses
  9. System.Classes,
  10. System.SysUtils,
  11. GLS.HeightData;
  12. //----------- GLHeightTileFile types and classes ------------------
  13. (*
  14. Access to large tiled height data files.
  15. Performance vs Raw file accesses (for perfect tile match):
  16. Cached data:
  17. "Smooth" terrain 1:2 to 1:10
  18. Random terrain 1:1
  19. Non-cached data:
  20. "Smooth" terrain 1:100 to 1:1000
  21. Random terrain 1:100
  22. *)
  23. type
  24. TIntegerArray = array [0 .. MaxInt shr 3] of Integer;
  25. PIntegerArray = ^TIntegerArray;
  26. TSmallIntArray = array [0 .. MaxInt shr 2] of SmallInt;
  27. PSmallIntArray = ^TSmallIntArray;
  28. TShortIntArray = array [0 .. MaxInt shr 2] of ShortInt;
  29. PShortIntArray = ^TShortIntArray;
  30. TGLHeightTileInfo = packed record
  31. left, top, width, height: Integer;
  32. min, max, average: SmallInt;
  33. fileOffset: Int64; // offset to tile data in the file
  34. end;
  35. PGLHeightTileInfo = ^TGLHeightTileInfo;
  36. PPHeightTileInfo = ^PGLHeightTileInfo;
  37. TGLHeightTile = packed record
  38. info: TGLHeightTileInfo;
  39. data: array of SmallInt;
  40. end;
  41. PGLHeightTile = ^TGLHeightTile;
  42. THTFHeader = packed record
  43. FileVersion: array [0 .. 5] of AnsiChar;
  44. TileIndexOffset: Int64;
  45. SizeX, SizeY: Integer;
  46. TileSize: Integer;
  47. DefaultZ: SmallInt;
  48. end;
  49. const
  50. cHTFHashTableSize = 1023;
  51. cHTFQuadTableSize = 31;
  52. type
  53. // Interfaces a Tiled file
  54. TGLHeightTileFile = class(TObject)
  55. private
  56. FFile: TStream;
  57. FHeader: THTFHeader;
  58. FTileIndex: packed array of TGLHeightTileInfo;
  59. FTileMark: array of Cardinal;
  60. FLastMark: Cardinal;
  61. FHashTable: array [0 .. cHTFHashTableSize] of array of Integer;
  62. FQuadTable: array [0 .. cHTFQuadTableSize, 0 .. cHTFQuadTableSize] of array of Integer;
  63. FCreating: Boolean;
  64. FHeightTile: TGLHeightTile;
  65. FInBuf: array of ShortInt;
  66. protected
  67. function GetTiles(index: Integer): PGLHeightTileInfo;
  68. function QuadTableX(x: Integer): Integer;
  69. function QuadTableY(y: Integer): Integer;
  70. procedure PackTile(aWidth, aHeight: Integer; src: PSmallIntArray);
  71. procedure UnPackTile(source: PShortIntArray);
  72. property TileIndexOffset: Int64 read FHeader.TileIndexOffset write FHeader.TileIndexOffset;
  73. public
  74. (* Creates a new HTF file.
  75. Read and data access methods are not available when creating. *)
  76. constructor CreateNew(const fileName: String; aSizeX, aSizeY, aTileSize: Integer);
  77. constructor Create(const fileName: String);
  78. destructor Destroy; override;
  79. // Returns tile index for corresponding left/top.
  80. function GetTileIndex(aLeft, aTop: Integer): Integer;
  81. // Returns tile of corresponding left/top.
  82. function GetTile(aLeft, aTop: Integer; pTileInfo: PPHeightTileInfo = nil): PGLHeightTile;
  83. (* Stores and compresses give tile data.
  84. aLeft and top MUST be a multiple of TileSize, aWidth and aHeight
  85. MUST be lower or equal to TileSize. *)
  86. procedure CompressTile(aLeft, aTop, aWidth, aHeight: Integer; aData: PSmallIntArray);
  87. (* Extract a single row from the HTF file.
  88. This is NOT the fastest way to access HTF data.
  89. All of the row must be contained in the world, otherwise result is undefined. *)
  90. procedure ExtractRow(x, y, len: Integer; dest: PSmallIntArray);
  91. // Returns the tile that contains x and y.
  92. function XYTileInfo(anX, anY: Integer): PGLHeightTileInfo;
  93. (* Returns the height at given coordinates.
  94. This is definetely NOT the fastest way to access HTF data and should
  95. only be used as utility function. *)
  96. function XYHeight(anX, anY: Integer): SmallInt;
  97. // Clears the list then add all tiles that overlap the rectangular area.
  98. procedure TilesInRect(aLeft, aTop, aRight, aBottom: Integer; destList: TList);
  99. function TileCount: Integer;
  100. property Tiles[index: Integer]: PGLHeightTileInfo read GetTiles;
  101. function IndexOfTile(aTile: PGLHeightTileInfo): Integer;
  102. function TileCompressedSize(tileIndex: Integer): Integer;
  103. property SizeX: Integer read FHeader.SizeX;
  104. property SizeY: Integer read FHeader.SizeY;
  105. (* Maximum width and height for a tile.
  106. Actual tiles may not be square, can assume random layouts, and may overlap. *)
  107. property TileSize: Integer read FHeader.TileSize;
  108. property DefaultZ: SmallInt read FHeader.DefaultZ write FHeader.DefaultZ;
  109. end;
  110. //---------------- TGLHeightTileFileHDS class -----------------------
  111. // An Height Data Source for the HTF format.
  112. TGLHeightTileFileHDS = class(TGLHeightDataSource)
  113. private
  114. FInfiniteWrap: Boolean;
  115. FInverted: Boolean;
  116. FHTFFileName: String;
  117. FHTF: TGLHeightTileFile;
  118. FMinElevation: Integer;
  119. protected
  120. procedure SetHTFFileName(const val: String);
  121. procedure SetInfiniteWrap(val: Boolean);
  122. procedure SetInverted(val: Boolean);
  123. procedure SetMinElevation(val: Integer);
  124. public
  125. constructor Create(AOwner: TComponent); override;
  126. destructor Destroy; override;
  127. procedure StartPreparingData(HeightData: TGLHeightData); override;
  128. function Width: Integer; override;
  129. function Height: Integer; override;
  130. function OpenHTF: TGLHeightTileFile;
  131. // gives you direct access to the HTF object
  132. published
  133. (* FileName of the HTF file.
  134. Note that it is accessed via the services of GLS.ApplicationFileIO,
  135. so this may not necessarily be a regular file on a disk... *)
  136. property HTFFileName: String read FHTFFileName write SetHTFFileName;
  137. // If true the height field is wrapped indefinetely.
  138. property InfiniteWrap: Boolean read FInfiniteWrap write SetInfiniteWrap
  139. default True;
  140. // If true the height data is inverted.(Top to bottom)
  141. property Inverted: Boolean read FInverted write SetInverted default True;
  142. (* Minimum elevation of the tiles that are considered to exist.
  143. This property can typically be used to hide underwater tiles. *)
  144. property MinElevation: Integer read FMinElevation write SetMinElevation
  145. default -32768;
  146. property MaxPoolSize;
  147. property DefaultHeight;
  148. end;
  149. // ------------------------------------------------------------------
  150. implementation
  151. // ------------------------------------------------------------------
  152. const
  153. cFileVersion = 'HTF100';
  154. procedure FillSmallInt(p: PSmallInt; count: Integer; v: SmallInt);
  155. var
  156. I: Integer;
  157. begin
  158. for I := count - 1 downto 0 do
  159. begin
  160. p^ := v;
  161. Inc(p);
  162. end;
  163. end;
  164. // ------------------
  165. // ------------------ TGLHeightTileFile ------------------
  166. // ------------------
  167. constructor TGLHeightTileFile.CreateNew(const fileName: String;
  168. aSizeX, aSizeY, aTileSize: Integer);
  169. begin
  170. with FHeader do
  171. begin
  172. FileVersion := cFileVersion;
  173. SizeX := aSizeX;
  174. SizeY := aSizeY;
  175. TileSize := aTileSize;
  176. end;
  177. FFile := TFileStream.Create(fileName, fmCreate);
  178. FFile.Write(FHeader, SizeOf(FHeader));
  179. FCreating := True;
  180. SetLength(FHeightTile.data, aTileSize * aTileSize);
  181. end;
  182. constructor TGLHeightTileFile.Create(const fileName: String);
  183. var
  184. n, I, key, qx, qy: Integer;
  185. begin
  186. FFile := TFileStream.Create(fileName, fmOpenRead + fmShareDenyNone);
  187. // Read Header
  188. FFile.Read(FHeader, SizeOf(FHeader));
  189. if FHeader.FileVersion <> cFileVersion then
  190. raise Exception.Create('Invalid file type');
  191. // Read TileIndex
  192. FFile.Position := TileIndexOffset;
  193. FFile.Read(n, 4);
  194. SetLength(FTileIndex, n);
  195. FFile.Read(FTileIndex[0], SizeOf(TGLHeightTileInfo) * n);
  196. // Prepare HashTable & QuadTable
  197. for n := 0 to High(FTileIndex) do
  198. begin
  199. with FTileIndex[n] do
  200. begin
  201. key := left + (top shl 4);
  202. key := ((key and cHTFHashTableSize) + (key shr 10) + (key shr 20)) and
  203. cHTFHashTableSize;
  204. I := Length(FHashTable[key]);
  205. SetLength(FHashTable[key], I + 1);
  206. FHashTable[key][I] := n;
  207. for qx := QuadTableX(left) to QuadTableX(left + width - 1) do
  208. begin
  209. for qy := QuadTableY(top) to QuadTableY(top + height - 1) do
  210. begin
  211. I := Length(FQuadTable[qx, qy]);
  212. SetLength(FQuadTable[qx, qy], I + 1);
  213. FQuadTable[qx, qy][I] := n;
  214. end;
  215. end;
  216. end;
  217. end;
  218. FHeightTile.info.left := MaxInt; // mark as not loaded
  219. SetLength(FHeightTile.data, TileSize * TileSize);
  220. SetLength(FInBuf, TileSize * (TileSize + 1) * 2);
  221. SetLength(FTileMark, Length(FTileIndex));
  222. end;
  223. destructor TGLHeightTileFile.Destroy;
  224. var
  225. n: Integer;
  226. begin
  227. if FCreating then
  228. begin
  229. TileIndexOffset := FFile.Position;
  230. // write tile index
  231. n := Length(FTileIndex);
  232. FFile.Write(n, 4);
  233. FFile.Write(FTileIndex[0], SizeOf(TGLHeightTileInfo) * n);
  234. // write data size
  235. FFile.Position := 0;
  236. FFile.Write(FHeader, SizeOf(FHeader));
  237. end;
  238. FFile.Free;
  239. inherited Destroy;
  240. end;
  241. function TGLHeightTileFile.QuadTableX(x: Integer): Integer;
  242. begin
  243. Result := ((x * (cHTFQuadTableSize + 1)) div (SizeX + 1)) and cHTFQuadTableSize;
  244. end;
  245. function TGLHeightTileFile.QuadTableY(y: Integer): Integer;
  246. begin
  247. Result := ((y * (cHTFQuadTableSize + 1)) div (SizeY + 1)) and cHTFQuadTableSize;
  248. end;
  249. procedure TGLHeightTileFile.PackTile(aWidth, aHeight: Integer; src: PSmallIntArray);
  250. var
  251. packWidth: Integer;
  252. function DiffEncode(src: PSmallIntArray; dest: PShortIntArray): Cardinal;
  253. var
  254. I: Integer;
  255. v, delta: SmallInt;
  256. begin
  257. Result := Cardinal(dest);
  258. v := src[0];
  259. PSmallIntArray(dest)[0] := v;
  260. dest := PShortIntArray(Cardinal(dest) + 2);
  261. I := 1;
  262. while I < packWidth do
  263. begin
  264. delta := src[I] - v;
  265. v := src[I];
  266. if Abs(delta) <= 127 then
  267. begin
  268. dest[0] := ShortInt(delta);
  269. dest := PShortIntArray(Cardinal(dest) + 1);
  270. end
  271. else
  272. begin
  273. dest[0] := -128;
  274. dest := PShortIntArray(Cardinal(dest) + 1);
  275. PSmallIntArray(dest)[0] := v;
  276. dest := PShortIntArray(Cardinal(dest) + 2);
  277. end;
  278. Inc(I);
  279. end;
  280. Result := Cardinal(dest) - Result;
  281. end;
  282. function RLEEncode(src: PSmallIntArray; dest: PAnsiChar): Cardinal;
  283. var
  284. v: SmallInt;
  285. I, n: Integer;
  286. begin
  287. I := 0;
  288. Result := Cardinal(dest);
  289. while (I < packWidth) do
  290. begin
  291. v := src[I];
  292. Inc(I);
  293. n := 0;
  294. PSmallIntArray(dest)[0] := v;
  295. Inc(dest, 2);
  296. while (src[I] = v) and (I < packWidth) do
  297. begin
  298. Inc(n);
  299. if n = 255 then
  300. begin
  301. dest[0] := #255;
  302. Inc(dest);
  303. n := 0;
  304. end;
  305. Inc(I);
  306. end;
  307. if (I < packWidth) or (n > 0) then
  308. begin
  309. dest[0] := AnsiChar(n);
  310. Inc(dest);
  311. end;
  312. end;
  313. Result := Cardinal(dest) - Result;
  314. end;
  315. var
  316. y: Integer;
  317. p: PSmallIntArray;
  318. buf, bestBuf: array of Byte;
  319. bestLength, len: Integer;
  320. leftPack, rightPack: Byte;
  321. bestMethod: Byte; // 0=RAW, 1=Diff, 2=RLE
  322. av: Int64;
  323. v: SmallInt;
  324. begin
  325. SetLength(buf, TileSize * 4); // worst case situation
  326. SetLength(bestBuf, TileSize * 4); // worst case situation
  327. with FHeightTile.info do
  328. begin
  329. min := src[0];
  330. max := src[0];
  331. av := src[0];
  332. for y := 1 to aWidth * aHeight - 1 do
  333. begin
  334. v := src[y];
  335. if v < min then
  336. min := v
  337. else if v > max then
  338. max := v;
  339. av := av + v;
  340. end;
  341. average := av div (aWidth * aHeight);
  342. if min = max then
  343. Exit; // no need to store anything
  344. end;
  345. for y := 0 to aHeight - 1 do
  346. begin
  347. p := @src[aWidth * y];
  348. packWidth := aWidth;
  349. // Lookup leftPack
  350. leftPack := 0;
  351. while (leftPack < 255) and (packWidth > 0) and (p[0] = DefaultZ) do
  352. begin
  353. p := PSmallIntArray(Cardinal(p) + 2);
  354. Dec(packWidth);
  355. Inc(leftPack);
  356. end;
  357. // Lookup rightPack
  358. rightPack := 0;
  359. while (rightPack < 255) and (packWidth > 0) and
  360. (p[packWidth - 1] = DefaultZ) do
  361. begin
  362. Dec(packWidth);
  363. Inc(rightPack);
  364. end;
  365. // Default encoding = RAW
  366. bestLength := packWidth * 2;
  367. bestMethod := 0;
  368. Move(p^, bestBuf[0], bestLength);
  369. // Diff encoding
  370. len := DiffEncode(p, PShortIntArray(@buf[0]));
  371. if len < bestLength then
  372. begin
  373. bestLength := len;
  374. bestMethod := 1;
  375. Move(buf[0], bestBuf[0], bestLength);
  376. end;
  377. // RLE encoding
  378. len := RLEEncode(p, PAnsiChar(@buf[0]));
  379. if len < bestLength then
  380. begin
  381. bestLength := len;
  382. bestMethod := 2;
  383. Move(buf[0], bestBuf[0], bestLength);
  384. end;
  385. // Write to file
  386. if (leftPack or rightPack) = 0 then
  387. begin
  388. FFile.Write(bestMethod, 1);
  389. FFile.Write(bestBuf[0], bestLength);
  390. end
  391. else
  392. begin
  393. if leftPack > 0 then
  394. begin
  395. if rightPack > 0 then
  396. begin
  397. bestMethod := bestMethod + $C0;
  398. FFile.Write(bestMethod, 1);
  399. FFile.Write(leftPack, 1);
  400. FFile.Write(rightPack, 1);
  401. FFile.Write(bestBuf[0], bestLength);
  402. end
  403. else
  404. begin
  405. bestMethod := bestMethod + $80;
  406. FFile.Write(bestMethod, 1);
  407. FFile.Write(leftPack, 1);
  408. FFile.Write(bestBuf[0], bestLength);
  409. end;
  410. end
  411. else
  412. begin
  413. bestMethod := bestMethod + $40;
  414. FFile.Write(bestMethod, 1);
  415. FFile.Write(rightPack, 1);
  416. FFile.Write(bestBuf[0], bestLength);
  417. end;
  418. end;
  419. end;
  420. end;
  421. procedure TGLHeightTileFile.UnPackTile(source: PShortIntArray);
  422. var
  423. unpackWidth, tileWidth: Cardinal;
  424. src: PShortInt;
  425. dest: PSmallInt;
  426. procedure DiffDecode;
  427. var
  428. v: SmallInt;
  429. delta: SmallInt;
  430. locSrc: PShortInt;
  431. destEnd, locDest: PSmallInt;
  432. begin
  433. locSrc := PShortInt(Cardinal(src) - 1);
  434. locDest := dest;
  435. destEnd := PSmallInt(Cardinal(dest) + unpackWidth * 2);
  436. while Cardinal(locDest) < Cardinal(destEnd) do
  437. begin
  438. Inc(locSrc);
  439. v := PSmallInt(locSrc)^;
  440. Inc(locSrc, 2);
  441. locDest^ := v;
  442. Inc(locDest);
  443. while (Cardinal(locDest) < Cardinal(destEnd)) do
  444. begin
  445. delta := locSrc^;
  446. if delta <> -128 then
  447. begin
  448. v := v + delta;
  449. Inc(locSrc);
  450. locDest^ := v;
  451. Inc(locDest);
  452. end
  453. else
  454. Break;
  455. end;
  456. end;
  457. src := locSrc;
  458. dest := locDest;
  459. end;
  460. procedure RLEDecode;
  461. var
  462. n, j: Cardinal;
  463. v: SmallInt;
  464. locSrc: PShortInt;
  465. destEnd, locDest: PSmallInt;
  466. begin
  467. locSrc := src;
  468. locDest := dest;
  469. destEnd := PSmallInt(Cardinal(dest) + unpackWidth * 2);
  470. while Cardinal(locDest) < Cardinal(destEnd) do
  471. begin
  472. v := PSmallIntArray(locSrc)[0];
  473. Inc(locSrc, 2);
  474. repeat
  475. if Cardinal(locDest) = Cardinal(destEnd) - 2 then
  476. begin
  477. locDest^ := v;
  478. Inc(locDest);
  479. n := 0;
  480. end
  481. else
  482. begin
  483. n := Integer(locSrc^ and 255);
  484. Inc(locSrc);
  485. for j := 0 to n do
  486. begin
  487. locDest^ := v;
  488. Inc(locDest);
  489. end;
  490. end;
  491. until (n < 255) or (Cardinal(locDest) >= Cardinal(destEnd));
  492. end;
  493. src := locSrc;
  494. dest := locDest;
  495. end;
  496. var
  497. y: Integer;
  498. n: Byte;
  499. method: Byte;
  500. begin
  501. dest := @FHeightTile.data[0];
  502. with FHeightTile.info do
  503. begin
  504. if min = max then
  505. begin
  506. FillSmallInt(dest, width * height, min);
  507. Exit;
  508. end;
  509. tileWidth := width;
  510. end;
  511. src := PShortInt(source);
  512. n := 0;
  513. for y := 0 to FHeightTile.info.height - 1 do
  514. begin
  515. method := Byte(src^);
  516. Inc(src);
  517. unpackWidth := tileWidth;
  518. // Process left pack if any
  519. if (method and $80) <> 0 then
  520. begin
  521. n := PByte(src)^;
  522. Inc(src);
  523. FillSmallInt(dest, n, DefaultZ);
  524. Dec(unpackWidth, n);
  525. Inc(dest, n);
  526. end;
  527. // Read right pack if any
  528. if (method and $40) <> 0 then
  529. begin
  530. PByte(@n)^ := PByte(src)^;
  531. Inc(src);
  532. Dec(unpackWidth, n)
  533. end
  534. else
  535. n := 0;
  536. // Process main data
  537. case (method and $3F) of
  538. 1:
  539. DiffDecode;
  540. 2:
  541. RLEDecode;
  542. else
  543. Move(src^, dest^, unpackWidth * 2);
  544. Inc(src, unpackWidth * 2);
  545. Inc(dest, unpackWidth);
  546. end;
  547. // Process right pack if any
  548. if n > 0 then
  549. begin
  550. FillSmallInt(dest, n, DefaultZ);
  551. Inc(dest, n);
  552. end;
  553. end;
  554. end;
  555. function TGLHeightTileFile.GetTileIndex(aLeft, aTop: Integer): Integer;
  556. var
  557. I, key, n: Integer;
  558. p: PIntegerArray;
  559. begin
  560. Result := -1;
  561. key := aLeft + (aTop shl 4);
  562. key := ((key and cHTFHashTableSize) + (key shr 10) + (key shr 20)) and
  563. cHTFHashTableSize;
  564. n := Length(FHashTable[key]);
  565. if n > 0 then
  566. begin
  567. p := @FHashTable[key][0];
  568. for I := 0 to n - 1 do
  569. begin
  570. with FTileIndex[p[I]] do
  571. begin
  572. if (left = aLeft) and (top = aTop) then
  573. begin
  574. Result := p[I];
  575. Break;
  576. end;
  577. end;
  578. end;
  579. end;
  580. end;
  581. function TGLHeightTileFile.GetTile(aLeft, aTop: Integer;
  582. pTileInfo: PPHeightTileInfo = nil): PGLHeightTile;
  583. var
  584. I, n: Integer;
  585. tileInfo: PGLHeightTileInfo;
  586. begin
  587. with FHeightTile.info do
  588. if (left = aLeft) and (top = aTop) then
  589. begin
  590. Result := @FHeightTile;
  591. if Assigned(pTileInfo) then
  592. pTileInfo^ := @Result.info;
  593. Exit;
  594. end;
  595. I := GetTileIndex(aLeft, aTop);
  596. if I >= 0 then
  597. begin
  598. tileInfo := @FTileIndex[I];
  599. if Assigned(pTileInfo) then
  600. pTileInfo^ := tileInfo;
  601. if I < High(FTileIndex) then
  602. n := FTileIndex[I + 1].fileOffset - tileInfo.fileOffset
  603. else
  604. n := TileIndexOffset - tileInfo.fileOffset;
  605. Result := @FHeightTile;
  606. FHeightTile.info := tileInfo^;
  607. FFile.Position := tileInfo.fileOffset;
  608. FFile.Read(FInBuf[0], n);
  609. UnPackTile(@FInBuf[0]);
  610. end
  611. else
  612. begin
  613. Result := nil;
  614. if Assigned(pTileInfo) then
  615. pTileInfo^ := nil;
  616. end;
  617. end;
  618. procedure TGLHeightTileFile.CompressTile(aLeft, aTop, aWidth, aHeight: Integer;
  619. aData: PSmallIntArray);
  620. begin
  621. Assert(aWidth <= TileSize);
  622. Assert(aHeight <= TileSize);
  623. with FHeightTile.info do
  624. begin
  625. left := aLeft;
  626. top := aTop;
  627. width := aWidth;
  628. height := aHeight;
  629. fileOffset := FFile.Position;
  630. end;
  631. PackTile(aWidth, aHeight, aData);
  632. SetLength(FTileIndex, Length(FTileIndex) + 1);
  633. FTileIndex[High(FTileIndex)] := FHeightTile.info
  634. end;
  635. procedure TGLHeightTileFile.ExtractRow(x, y, len: Integer;
  636. dest: PSmallIntArray);
  637. var
  638. rx: Integer;
  639. n: Cardinal;
  640. tileInfo: PGLHeightTileInfo;
  641. tile: PGLHeightTile;
  642. begin
  643. while len > 0 do
  644. begin
  645. tileInfo := XYTileInfo(x, y);
  646. if not Assigned(tileInfo) then
  647. Exit;
  648. rx := x - tileInfo.left;
  649. n := Cardinal(tileInfo.width - rx);
  650. if n > Cardinal(len) then
  651. n := Cardinal(len);
  652. tile := GetTile(tileInfo.left, tileInfo.top);
  653. Move(tile.data[(y - tileInfo.top) * tileInfo.width + rx], dest^, n * 2);
  654. dest := PSmallIntArray(Cardinal(dest) + n * 2);
  655. Dec(len, n);
  656. Inc(x, n);
  657. end;
  658. end;
  659. function TGLHeightTileFile.XYTileInfo(anX, anY: Integer): PGLHeightTileInfo;
  660. var
  661. tileList: TList;
  662. begin
  663. tileList := TList.Create;
  664. try
  665. TilesInRect(anX, anY, anX + 1, anY + 1, tileList);
  666. if tileList.count > 0 then
  667. Result := PGLHeightTileInfo(tileList.First)
  668. else
  669. Result := nil;
  670. finally
  671. tileList.Free;
  672. end;
  673. end;
  674. function TGLHeightTileFile.XYHeight(anX, anY: Integer): SmallInt;
  675. var
  676. tileInfo: PGLHeightTileInfo;
  677. tile: PGLHeightTile;
  678. begin
  679. // Current tile per chance?
  680. with FHeightTile.info do
  681. begin
  682. if (left <= anX) and (left + width > anX) and (top <= anY) and
  683. (top + height > anY) then
  684. begin
  685. Result := FHeightTile.data[(anX - left) + (anY - top) * width];
  686. Exit;
  687. end;
  688. end;
  689. // Find corresponding tile if any
  690. tileInfo := XYTileInfo(anX, anY);
  691. if Assigned(tileInfo) then
  692. with tileInfo^ do
  693. begin
  694. tile := GetTile(left, top);
  695. Result := tile.data[(anX - left) + (anY - top) * width];
  696. end
  697. else
  698. Result := DefaultZ;
  699. end;
  700. procedure TGLHeightTileFile.TilesInRect(aLeft, aTop, aRight, aBottom: Integer;
  701. destList: TList);
  702. var
  703. I, n, qx, qy, idx: Integer;
  704. p: PIntegerArray;
  705. tileInfo: PGLHeightTileInfo;
  706. begin
  707. destList.count := 0;
  708. // Clamp to world
  709. if (aLeft > SizeX) or (aRight < 0) or (aTop > SizeY) or (aBottom < 0) then
  710. Exit;
  711. if aLeft < 0 then
  712. aLeft := 0;
  713. if aRight > SizeX then
  714. aRight := SizeX;
  715. if aTop < 0 then
  716. aTop := 0;
  717. if aBottom > SizeY then
  718. aBottom := SizeY;
  719. // Collect tiles on quads
  720. Inc(FLastMark);
  721. for qy := QuadTableY(aTop) to QuadTableY(aBottom) do
  722. begin
  723. for qx := QuadTableX(aLeft) to QuadTableX(aRight) do
  724. begin
  725. n := High(FQuadTable[qx, qy]);
  726. p := @FQuadTable[qx, qy][0];
  727. for I := 0 to n do
  728. begin
  729. idx := p[I];
  730. if FTileMark[idx] <> FLastMark then
  731. begin
  732. FTileMark[idx] := FLastMark;
  733. tileInfo := @FTileIndex[idx];
  734. with tileInfo^ do
  735. begin
  736. if (left <= aRight) and (top <= aBottom) and (aLeft < left + width)
  737. and (aTop < top + height) then
  738. destList.Add(tileInfo);
  739. end;
  740. end;
  741. end;
  742. end;
  743. end;
  744. end;
  745. function TGLHeightTileFile.TileCount: Integer;
  746. begin
  747. Result := Length(FTileIndex);
  748. end;
  749. function TGLHeightTileFile.GetTiles(index: Integer): PGLHeightTileInfo;
  750. begin
  751. Result := @FTileIndex[index];
  752. end;
  753. function TGLHeightTileFile.IndexOfTile(aTile: PGLHeightTileInfo): Integer;
  754. var
  755. c: Cardinal;
  756. begin
  757. c := Cardinal(aTile) - Cardinal(@FTileIndex[0]);
  758. if (c mod SizeOf(TGLHeightTileInfo)) = 0 then
  759. begin
  760. Result := (c div SizeOf(TGLHeightTileInfo));
  761. if (Result < 0) or (Result > High(FTileIndex)) then
  762. Result := -1;
  763. end
  764. else
  765. Result := -1;
  766. end;
  767. function TGLHeightTileFile.TileCompressedSize(tileIndex: Integer): Integer;
  768. begin
  769. if tileIndex < High(FTileIndex) then
  770. Result := FTileIndex[tileIndex + 1].fileOffset - FTileIndex[tileIndex]
  771. .fileOffset
  772. else
  773. Result := TileIndexOffset - FTileIndex[tileIndex].fileOffset;
  774. end;
  775. //-------------------- TGLHeightTileFileHDS -------------------
  776. constructor TGLHeightTileFileHDS.Create(AOwner: TComponent);
  777. begin
  778. inherited Create(AOwner);
  779. FInfiniteWrap := True;
  780. FInverted := True;
  781. FMinElevation := -32768;
  782. end;
  783. destructor TGLHeightTileFileHDS.Destroy;
  784. begin
  785. FHTF.Free;
  786. inherited Destroy;
  787. end;
  788. procedure TGLHeightTileFileHDS.SetHTFFileName(const val: String);
  789. begin
  790. if FHTFFileName <> val then
  791. begin
  792. MarkDirty;
  793. FreeAndNil(FHTF);
  794. FHTFFileName := val;
  795. end;
  796. end;
  797. procedure TGLHeightTileFileHDS.SetInfiniteWrap(val: Boolean);
  798. begin
  799. if FInfiniteWrap = val then
  800. exit;
  801. FInfiniteWrap := val;
  802. MarkDirty;
  803. end;
  804. procedure TGLHeightTileFileHDS.SetInverted(val: Boolean);
  805. begin
  806. if FInverted = val then
  807. exit;
  808. FInverted := val;
  809. MarkDirty;
  810. end;
  811. procedure TGLHeightTileFileHDS.SetMinElevation(val: Integer);
  812. begin
  813. if FMinElevation <> val then
  814. begin
  815. FMinElevation := val;
  816. MarkDirty;
  817. end;
  818. end;
  819. // Tries to open the assigned HeightTileFile.
  820. //
  821. function TGLHeightTileFileHDS.OpenHTF: TGLHeightTileFile;
  822. begin
  823. if not Assigned(FHTF) then
  824. begin
  825. if FHTFFileName = '' then
  826. FHTF := nil
  827. else
  828. FHTF := TGLHeightTileFile.Create(FHTFFileName);
  829. end;
  830. result := FHTF;
  831. end;
  832. procedure TGLHeightTileFileHDS.StartPreparingData(HeightData: TGLHeightData);
  833. var
  834. oldType: TGLHeightDataType;
  835. htfTile: PGLHeightTile;
  836. htfTileInfo: PGLHeightTileInfo;
  837. x, y: Integer;
  838. YPos: Integer;
  839. inY, outY: Integer;
  840. PLineIn, PLineOut: ^PSmallIntArray;
  841. LineDataSize: Integer;
  842. begin
  843. // access htf data
  844. if OpenHTF = nil then
  845. begin
  846. HeightData.DataState := hdsNone;
  847. exit;
  848. end
  849. else
  850. Assert(FHTF.TileSize = HeightData.Size,
  851. 'HTF TileSize and HeightData size don''t match.(' +
  852. IntToStr(FHTF.TileSize) + ' and ' + IntToStr(HeightData.Size) + ')');
  853. HeightData.DataState := hdsPreparing;
  854. // retrieve data and place it in the HeightData
  855. with HeightData do
  856. begin
  857. if Inverted then
  858. YPos := YTop
  859. else
  860. YPos := FHTF.SizeY - YTop - Size + 1;
  861. if InfiniteWrap then
  862. begin
  863. x := XLeft mod FHTF.SizeX;
  864. if x < 0 then
  865. x := x + FHTF.SizeX;
  866. y := YPos mod FHTF.SizeY;
  867. if y < 0 then
  868. y := y + FHTF.SizeY;
  869. htfTile := FHTF.GetTile(x, y, @htfTileInfo);
  870. end
  871. else
  872. begin
  873. htfTile := FHTF.GetTile(XLeft, YPos, @htfTileInfo);
  874. end;
  875. if (htfTile = nil) or (htfTileInfo.max <= FMinElevation) then
  876. begin
  877. // non-aligned tiles aren't handled (would be slow anyway)
  878. DataState := hdsNone;
  879. end
  880. else
  881. begin
  882. oldType := DataType;
  883. Allocate(hdtSmallInt);
  884. if Inverted then
  885. Move(htfTile.data[0], SmallIntData^, DataSize)
  886. else
  887. begin // invert the terrain (top to bottom) To compensate for the inverted terrain renderer
  888. LineDataSize := DataSize div Size;
  889. for y := 0 to Size - 1 do
  890. begin
  891. inY := y * HeightData.Size;
  892. outY := ((Size - 1) - y) * HeightData.Size;
  893. PLineIn := @htfTile.data[inY];
  894. PLineOut := @HeightData.SmallIntData[outY];
  895. Move(PLineIn^, PLineOut^, LineDataSize);
  896. end;
  897. end;
  898. // ---Move(htfTile.data[0], SmallIntData^, DataSize);---
  899. if oldType <> hdtSmallInt then
  900. DataType := oldType;
  901. TextureCoordinates(HeightData);
  902. inherited;
  903. HeightMin := htfTileInfo.min;
  904. HeightMax := htfTileInfo.max;
  905. end;
  906. end;
  907. end;
  908. function TGLHeightTileFileHDS.Width: Integer;
  909. begin
  910. if OpenHTF = nil then
  911. result := 0
  912. else
  913. result := FHTF.SizeX;
  914. end;
  915. function TGLHeightTileFileHDS.Height: Integer;
  916. begin
  917. if OpenHTF = nil then
  918. result := 0
  919. else
  920. result := FHTF.SizeY;
  921. end;
  922. // ------------------------------------------------------------------
  923. initialization
  924. // ------------------------------------------------------------------
  925. RegisterClasses([TGLHeightTileFileHDS]);
  926. end.