GXS.HeightTileFile.pas 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. //
  2. // The graphics engine GXScene https://github.com/glscene
  3. //
  4. unit GXS.HeightTileFile;
  5. (*
  6. Access to large tiled height data files.
  7. Performance vs Raw file accesses (for perfect tile match):
  8. Cached data:
  9. "Smooth" terrain 1:2 to 1:10
  10. Random terrain 1:1
  11. Non-cached data:
  12. "Smooth" terrain 1:100 to 1:1000
  13. Random terrain 1:100
  14. *)
  15. interface
  16. {$I Stage.Defines.inc}
  17. uses
  18. System.Classes,
  19. System.SysUtils,
  20. GXS.ApplicationFileIO;
  21. type
  22. TIntegerArray = array [0 .. MaxInt shr 3] of Integer;
  23. PIntegerArray = ^TIntegerArray;
  24. TSmallIntArray = array [0 .. MaxInt shr 2] of SmallInt;
  25. PSmallIntArray = ^TSmallIntArray;
  26. TShortIntArray = array [0 .. MaxInt shr 2] of ShortInt;
  27. PShortIntArray = ^TShortIntArray;
  28. TgxHeightTileInfo = packed record
  29. left, top, width, height: Integer;
  30. min, max, average: SmallInt;
  31. fileOffset: Int64; // offset to tile data in the file
  32. end;
  33. PgxHeightTileInfo = ^TgxHeightTileInfo;
  34. PPHeightTileInfo = ^PgxHeightTileInfo;
  35. TgxHeightTile = packed record
  36. info: TgxHeightTileInfo;
  37. data: array of SmallInt;
  38. end;
  39. PHeightTile = ^TgxHeightTile;
  40. THTFHeader = packed record
  41. FileVersion: array [0 .. 5] of AnsiChar;
  42. TileIndexOffset: Int64;
  43. SizeX, SizeY: Integer;
  44. TileSize: Integer;
  45. DefaultZ: SmallInt;
  46. end;
  47. const
  48. cHTFHashTableSize = 1023;
  49. cHTFQuadTableSize = 31;
  50. type
  51. // Interfaces a Tiled file
  52. TgxHeightTileFile = class(TObject)
  53. private
  54. FFile: TStream;
  55. FHeader: THTFHeader;
  56. FTileIndex: packed array of TgxHeightTileInfo;
  57. FTileMark: array of Cardinal;
  58. FLastMark: Cardinal;
  59. FHashTable: array [0 .. cHTFHashTableSize] of array of Integer;
  60. FQuadTable: array [0 .. cHTFQuadTableSize, 0 .. cHTFQuadTableSize]
  61. of array of Integer;
  62. FCreating: Boolean;
  63. FHeightTile: TgxHeightTile;
  64. FInBuf: array of ShortInt;
  65. protected
  66. function GetTiles(index: Integer): PgxHeightTileInfo;
  67. function QuadTableX(x: Integer): Integer;
  68. function QuadTableY(y: Integer): Integer;
  69. procedure PackTile(aWidth, aHeight: Integer; src: PSmallIntArray);
  70. procedure UnPackTile(source: PShortIntArray);
  71. property TileIndexOffset: Int64 read FHeader.TileIndexOffset
  72. 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;
  77. aSizeX, aSizeY, aTileSize: Integer);
  78. constructor Create(const fileName: String);
  79. destructor Destroy; override;
  80. // Returns tile index for corresponding left/top.
  81. function GetTileIndex(aLeft, aTop: Integer): Integer;
  82. // Returns tile of corresponding left/top.
  83. function GetTile(aLeft, aTop: Integer; pTileInfo: PPHeightTileInfo = nil)
  84. : PHeightTile;
  85. (* Stores and compresses give tile data.
  86. aLeft and top MUST be a multiple of TileSize, aWidth and aHeight
  87. MUST be lower or equal to TileSize. *)
  88. procedure CompressTile(aLeft, aTop, aWidth, aHeight: Integer;
  89. aData: PSmallIntArray);
  90. (* Extract a single row from the HTF file.
  91. This is NOT the fastest way to access HTF data.
  92. All of the row must be contained in the world, otherwise result is undefined. *)
  93. procedure ExtractRow(x, y, len: Integer; dest: PSmallIntArray);
  94. // Returns the tile that contains x and y.
  95. function XYTileInfo(anX, anY: Integer): PgxHeightTileInfo;
  96. (* Returns the height at given coordinates.
  97. This is definetely NOT the fastest way to access HTF data and should
  98. only be used as utility function. *)
  99. function XYHeight(anX, anY: Integer): SmallInt;
  100. // Clears the list then add all tiles that overlap the rectangular area.
  101. procedure TilesInRect(aLeft, aTop, aRight, aBottom: Integer;
  102. destList: TList);
  103. function TileCount: Integer;
  104. property Tiles[index: Integer]: PgxHeightTileInfo read GetTiles;
  105. function IndexOfTile(aTile: PgxHeightTileInfo): Integer;
  106. function TileCompressedSize(tileIndex: Integer): Integer;
  107. property SizeX: Integer read FHeader.SizeX;
  108. property SizeY: Integer read FHeader.SizeY;
  109. (* Maximum width and height for a tile.
  110. Actual tiles may not be square, can assume random layouts, and may overlap. *)
  111. property TileSize: Integer read FHeader.TileSize;
  112. property DefaultZ: SmallInt read FHeader.DefaultZ write FHeader.DefaultZ;
  113. end;
  114. // ------------------------------------------------------------------
  115. implementation
  116. // ------------------------------------------------------------------
  117. const
  118. cFileVersion = 'HTF100';
  119. procedure FillSmallInt(p: PSmallInt; count: Integer; v: SmallInt);
  120. var
  121. I: Integer;
  122. begin
  123. for I := count - 1 downto 0 do
  124. begin
  125. p^ := v;
  126. Inc(p);
  127. end;
  128. end;
  129. // ------------------
  130. // ------------------ TgxHeightTileFile ------------------
  131. // ------------------
  132. constructor TgxHeightTileFile.CreateNew(const fileName: String;
  133. aSizeX, aSizeY, aTileSize: Integer);
  134. begin
  135. with FHeader do
  136. begin
  137. FileVersion := cFileVersion;
  138. SizeX := aSizeX;
  139. SizeY := aSizeY;
  140. TileSize := aTileSize;
  141. end;
  142. FFile := TFileStream.Create(fileName, fmCreate);
  143. FFile.Write(FHeader, SizeOf(FHeader));
  144. FCreating := True;
  145. SetLength(FHeightTile.data, aTileSize * aTileSize);
  146. end;
  147. constructor TgxHeightTileFile.Create(const fileName: String);
  148. var
  149. n, I, key, qx, qy: Integer;
  150. begin
  151. FFile := TFileStream.Create(fileName, fmOpenRead + fmShareDenyNone);
  152. // Read Header
  153. FFile.Read(FHeader, SizeOf(FHeader));
  154. if FHeader.FileVersion <> cFileVersion then
  155. raise Exception.Create('Invalid file type');
  156. // Read TileIndex
  157. FFile.Position := TileIndexOffset;
  158. FFile.Read(n, 4);
  159. SetLength(FTileIndex, n);
  160. FFile.Read(FTileIndex[0], SizeOf(TgxHeightTileInfo) * n);
  161. // Prepare HashTable & QuadTable
  162. for n := 0 to High(FTileIndex) do
  163. begin
  164. with FTileIndex[n] do
  165. begin
  166. key := left + (top shl 4);
  167. key := ((key and cHTFHashTableSize) + (key shr 10) + (key shr 20)) and
  168. cHTFHashTableSize;
  169. I := Length(FHashTable[key]);
  170. SetLength(FHashTable[key], I + 1);
  171. FHashTable[key][I] := n;
  172. for qx := QuadTableX(left) to QuadTableX(left + width - 1) do
  173. begin
  174. for qy := QuadTableY(top) to QuadTableY(top + height - 1) do
  175. begin
  176. I := Length(FQuadTable[qx, qy]);
  177. SetLength(FQuadTable[qx, qy], I + 1);
  178. FQuadTable[qx, qy][I] := n;
  179. end;
  180. end;
  181. end;
  182. end;
  183. FHeightTile.info.left := MaxInt; // mark as not loaded
  184. SetLength(FHeightTile.data, TileSize * TileSize);
  185. SetLength(FInBuf, TileSize * (TileSize + 1) * 2);
  186. SetLength(FTileMark, Length(FTileIndex));
  187. end;
  188. destructor TgxHeightTileFile.Destroy;
  189. var
  190. n: Integer;
  191. begin
  192. if FCreating then
  193. begin
  194. TileIndexOffset := FFile.Position;
  195. // write tile index
  196. n := Length(FTileIndex);
  197. FFile.Write(n, 4);
  198. FFile.Write(FTileIndex[0], SizeOf(TgxHeightTileInfo) * n);
  199. // write data size
  200. FFile.Position := 0;
  201. FFile.Write(FHeader, SizeOf(FHeader));
  202. end;
  203. FFile.Free;
  204. inherited Destroy;
  205. end;
  206. function TgxHeightTileFile.QuadTableX(x: Integer): Integer;
  207. begin
  208. Result := ((x * (cHTFQuadTableSize + 1)) div (SizeX + 1)) and
  209. cHTFQuadTableSize;
  210. end;
  211. function TgxHeightTileFile.QuadTableY(y: Integer): Integer;
  212. begin
  213. Result := ((y * (cHTFQuadTableSize + 1)) div (SizeY + 1)) and
  214. cHTFQuadTableSize;
  215. end;
  216. procedure TgxHeightTileFile.PackTile(aWidth, aHeight: Integer;
  217. src: PSmallIntArray);
  218. var
  219. packWidth: Integer;
  220. function DiffEncode(src: PSmallIntArray; dest: PShortIntArray): Cardinal;
  221. var
  222. I: Integer;
  223. v, delta: SmallInt;
  224. begin
  225. Result := Cardinal(dest);
  226. v := src[0];
  227. PSmallIntArray(dest)[0] := v;
  228. dest := PShortIntArray(Cardinal(dest) + 2);
  229. I := 1;
  230. while I < packWidth do
  231. begin
  232. delta := src[I] - v;
  233. v := src[I];
  234. if Abs(delta) <= 127 then
  235. begin
  236. dest[0] := ShortInt(delta);
  237. dest := PShortIntArray(Cardinal(dest) + 1);
  238. end
  239. else
  240. begin
  241. dest[0] := -128;
  242. dest := PShortIntArray(Cardinal(dest) + 1);
  243. PSmallIntArray(dest)[0] := v;
  244. dest := PShortIntArray(Cardinal(dest) + 2);
  245. end;
  246. Inc(I);
  247. end;
  248. Result := Cardinal(dest) - Result;
  249. end;
  250. function RLEEncode(src: PSmallIntArray; dest: PAnsiChar): Cardinal;
  251. var
  252. v: SmallInt;
  253. I, n: Integer;
  254. begin
  255. I := 0;
  256. Result := Cardinal(dest);
  257. while (I < packWidth) do
  258. begin
  259. v := src[I];
  260. Inc(I);
  261. n := 0;
  262. PSmallIntArray(dest)[0] := v;
  263. Inc(dest, 2);
  264. while (src[I] = v) and (I < packWidth) do
  265. begin
  266. Inc(n);
  267. if n = 255 then
  268. begin
  269. dest[0] := #255;
  270. Inc(dest);
  271. n := 0;
  272. end;
  273. Inc(I);
  274. end;
  275. if (I < packWidth) or (n > 0) then
  276. begin
  277. dest[0] := AnsiChar(n);
  278. Inc(dest);
  279. end;
  280. end;
  281. Result := Cardinal(dest) - Result;
  282. end;
  283. var
  284. y: Integer;
  285. p: PSmallIntArray;
  286. buf, bestBuf: array of Byte;
  287. bestLength, len: Integer;
  288. leftPack, rightPack: Byte;
  289. bestMethod: Byte; // 0=RAW, 1=Diff, 2=RLE
  290. av: Int64;
  291. v: SmallInt;
  292. begin
  293. SetLength(buf, TileSize * 4); // worst case situation
  294. SetLength(bestBuf, TileSize * 4); // worst case situation
  295. with FHeightTile.info do
  296. begin
  297. min := src[0];
  298. max := src[0];
  299. av := src[0];
  300. for y := 1 to aWidth * aHeight - 1 do
  301. begin
  302. v := src[y];
  303. if v < min then
  304. min := v
  305. else if v > max then
  306. max := v;
  307. av := av + v;
  308. end;
  309. average := av div (aWidth * aHeight);
  310. if min = max then
  311. Exit; // no need to store anything
  312. end;
  313. for y := 0 to aHeight - 1 do
  314. begin
  315. p := @src[aWidth * y];
  316. packWidth := aWidth;
  317. // Lookup leftPack
  318. leftPack := 0;
  319. while (leftPack < 255) and (packWidth > 0) and (p[0] = DefaultZ) do
  320. begin
  321. p := PSmallIntArray(Cardinal(p) + 2);
  322. Dec(packWidth);
  323. Inc(leftPack);
  324. end;
  325. // Lookup rightPack
  326. rightPack := 0;
  327. while (rightPack < 255) and (packWidth > 0) and
  328. (p[packWidth - 1] = DefaultZ) do
  329. begin
  330. Dec(packWidth);
  331. Inc(rightPack);
  332. end;
  333. // Default encoding = RAW
  334. bestLength := packWidth * 2;
  335. bestMethod := 0;
  336. Move(p^, bestBuf[0], bestLength);
  337. // Diff encoding
  338. len := DiffEncode(p, PShortIntArray(@buf[0]));
  339. if len < bestLength then
  340. begin
  341. bestLength := len;
  342. bestMethod := 1;
  343. Move(buf[0], bestBuf[0], bestLength);
  344. end;
  345. // RLE encoding
  346. len := RLEEncode(p, PAnsiChar(@buf[0]));
  347. if len < bestLength then
  348. begin
  349. bestLength := len;
  350. bestMethod := 2;
  351. Move(buf[0], bestBuf[0], bestLength);
  352. end;
  353. // Write to file
  354. if (leftPack or rightPack) = 0 then
  355. begin
  356. FFile.Write(bestMethod, 1);
  357. FFile.Write(bestBuf[0], bestLength);
  358. end
  359. else
  360. begin
  361. if leftPack > 0 then
  362. begin
  363. if rightPack > 0 then
  364. begin
  365. bestMethod := bestMethod + $C0;
  366. FFile.Write(bestMethod, 1);
  367. FFile.Write(leftPack, 1);
  368. FFile.Write(rightPack, 1);
  369. FFile.Write(bestBuf[0], bestLength);
  370. end
  371. else
  372. begin
  373. bestMethod := bestMethod + $80;
  374. FFile.Write(bestMethod, 1);
  375. FFile.Write(leftPack, 1);
  376. FFile.Write(bestBuf[0], bestLength);
  377. end;
  378. end
  379. else
  380. begin
  381. bestMethod := bestMethod + $40;
  382. FFile.Write(bestMethod, 1);
  383. FFile.Write(rightPack, 1);
  384. FFile.Write(bestBuf[0], bestLength);
  385. end;
  386. end;
  387. end;
  388. end;
  389. procedure TgxHeightTileFile.UnPackTile(source: PShortIntArray);
  390. var
  391. unpackWidth, tileWidth: Cardinal;
  392. src: PShortInt;
  393. dest: PSmallInt;
  394. procedure DiffDecode;
  395. var
  396. v: SmallInt;
  397. delta: SmallInt;
  398. locSrc: PShortInt;
  399. destEnd, locDest: PSmallInt;
  400. begin
  401. locSrc := PShortInt(Cardinal(src) - 1);
  402. locDest := dest;
  403. destEnd := PSmallInt(Cardinal(dest) + unpackWidth * 2);
  404. while Cardinal(locDest) < Cardinal(destEnd) do
  405. begin
  406. Inc(locSrc);
  407. v := PSmallInt(locSrc)^;
  408. Inc(locSrc, 2);
  409. locDest^ := v;
  410. Inc(locDest);
  411. while (Cardinal(locDest) < Cardinal(destEnd)) do
  412. begin
  413. delta := locSrc^;
  414. if delta <> -128 then
  415. begin
  416. v := v + delta;
  417. Inc(locSrc);
  418. locDest^ := v;
  419. Inc(locDest);
  420. end
  421. else
  422. Break;
  423. end;
  424. end;
  425. src := locSrc;
  426. dest := locDest;
  427. end;
  428. procedure RLEDecode;
  429. var
  430. n, j: Cardinal;
  431. v: SmallInt;
  432. locSrc: PShortInt;
  433. destEnd, locDest: PSmallInt;
  434. begin
  435. locSrc := src;
  436. locDest := dest;
  437. destEnd := PSmallInt(Cardinal(dest) + unpackWidth * 2);
  438. while Cardinal(locDest) < Cardinal(destEnd) do
  439. begin
  440. v := PSmallIntArray(locSrc)[0];
  441. Inc(locSrc, 2);
  442. repeat
  443. if Cardinal(locDest) = Cardinal(destEnd) - 2 then
  444. begin
  445. locDest^ := v;
  446. Inc(locDest);
  447. n := 0;
  448. end
  449. else
  450. begin
  451. n := Integer(locSrc^ and 255);
  452. Inc(locSrc);
  453. for j := 0 to n do
  454. begin
  455. locDest^ := v;
  456. Inc(locDest);
  457. end;
  458. end;
  459. until (n < 255) or (Cardinal(locDest) >= Cardinal(destEnd));
  460. end;
  461. src := locSrc;
  462. dest := locDest;
  463. end;
  464. var
  465. y: Integer;
  466. n: Byte;
  467. method: Byte;
  468. begin
  469. dest := @FHeightTile.data[0];
  470. with FHeightTile.info do
  471. begin
  472. if min = max then
  473. begin
  474. FillSmallInt(dest, width * height, min);
  475. Exit;
  476. end;
  477. tileWidth := width;
  478. end;
  479. src := PShortInt(source);
  480. n := 0;
  481. for y := 0 to FHeightTile.info.height - 1 do
  482. begin
  483. method := Byte(src^);
  484. Inc(src);
  485. unpackWidth := tileWidth;
  486. // Process left pack if any
  487. if (method and $80) <> 0 then
  488. begin
  489. n := PByte(src)^;
  490. Inc(src);
  491. FillSmallInt(dest, n, DefaultZ);
  492. Dec(unpackWidth, n);
  493. Inc(dest, n);
  494. end;
  495. // Read right pack if any
  496. if (method and $40) <> 0 then
  497. begin
  498. PByte(@n)^ := PByte(src)^;
  499. Inc(src);
  500. Dec(unpackWidth, n)
  501. end
  502. else
  503. n := 0;
  504. // Process main data
  505. case (method and $3F) of
  506. 1:
  507. DiffDecode;
  508. 2:
  509. RLEDecode;
  510. else
  511. Move(src^, dest^, unpackWidth * 2);
  512. Inc(src, unpackWidth * 2);
  513. Inc(dest, unpackWidth);
  514. end;
  515. // Process right pack if any
  516. if n > 0 then
  517. begin
  518. FillSmallInt(dest, n, DefaultZ);
  519. Inc(dest, n);
  520. end;
  521. end;
  522. end;
  523. function TgxHeightTileFile.GetTileIndex(aLeft, aTop: Integer): Integer;
  524. var
  525. I, key, n: Integer;
  526. p: PIntegerArray;
  527. begin
  528. Result := -1;
  529. key := aLeft + (aTop shl 4);
  530. key := ((key and cHTFHashTableSize) + (key shr 10) + (key shr 20)) and
  531. cHTFHashTableSize;
  532. n := Length(FHashTable[key]);
  533. if n > 0 then
  534. begin
  535. p := @FHashTable[key][0];
  536. for I := 0 to n - 1 do
  537. begin
  538. with FTileIndex[p[I]] do
  539. begin
  540. if (left = aLeft) and (top = aTop) then
  541. begin
  542. Result := p[I];
  543. Break;
  544. end;
  545. end;
  546. end;
  547. end;
  548. end;
  549. function TgxHeightTileFile.GetTile(aLeft, aTop: Integer;
  550. pTileInfo: PPHeightTileInfo = nil): PHeightTile;
  551. var
  552. I, n: Integer;
  553. tileInfo: PgxHeightTileInfo;
  554. begin
  555. with FHeightTile.info do
  556. if (left = aLeft) and (top = aTop) then
  557. begin
  558. Result := @FHeightTile;
  559. if Assigned(pTileInfo) then
  560. pTileInfo^ := @Result.info;
  561. Exit;
  562. end;
  563. I := GetTileIndex(aLeft, aTop);
  564. if I >= 0 then
  565. begin
  566. tileInfo := @FTileIndex[I];
  567. if Assigned(pTileInfo) then
  568. pTileInfo^ := tileInfo;
  569. if I < High(FTileIndex) then
  570. n := FTileIndex[I + 1].fileOffset - tileInfo.fileOffset
  571. else
  572. n := TileIndexOffset - tileInfo.fileOffset;
  573. Result := @FHeightTile;
  574. FHeightTile.info := tileInfo^;
  575. FFile.Position := tileInfo.fileOffset;
  576. FFile.Read(FInBuf[0], n);
  577. UnPackTile(@FInBuf[0]);
  578. end
  579. else
  580. begin
  581. Result := nil;
  582. if Assigned(pTileInfo) then
  583. pTileInfo^ := nil;
  584. end;
  585. end;
  586. procedure TgxHeightTileFile.CompressTile(aLeft, aTop, aWidth, aHeight: Integer;
  587. aData: PSmallIntArray);
  588. begin
  589. Assert(aWidth <= TileSize);
  590. Assert(aHeight <= TileSize);
  591. with FHeightTile.info do
  592. begin
  593. left := aLeft;
  594. top := aTop;
  595. width := aWidth;
  596. height := aHeight;
  597. fileOffset := FFile.Position;
  598. end;
  599. PackTile(aWidth, aHeight, aData);
  600. SetLength(FTileIndex, Length(FTileIndex) + 1);
  601. FTileIndex[High(FTileIndex)] := FHeightTile.info
  602. end;
  603. procedure TgxHeightTileFile.ExtractRow(x, y, len: Integer;
  604. dest: PSmallIntArray);
  605. var
  606. rx: Integer;
  607. n: Cardinal;
  608. tileInfo: PgxHeightTileInfo;
  609. tile: PHeightTile;
  610. begin
  611. while len > 0 do
  612. begin
  613. tileInfo := XYTileInfo(x, y);
  614. if not Assigned(tileInfo) then
  615. Exit;
  616. rx := x - tileInfo.left;
  617. n := Cardinal(tileInfo.width - rx);
  618. if n > Cardinal(len) then
  619. n := Cardinal(len);
  620. tile := GetTile(tileInfo.left, tileInfo.top);
  621. Move(tile.data[(y - tileInfo.top) * tileInfo.width + rx], dest^, n * 2);
  622. dest := PSmallIntArray(Cardinal(dest) + n * 2);
  623. Dec(len, n);
  624. Inc(x, n);
  625. end;
  626. end;
  627. function TgxHeightTileFile.XYTileInfo(anX, anY: Integer): PgxHeightTileInfo;
  628. var
  629. tileList: TList;
  630. begin
  631. tileList := TList.Create;
  632. try
  633. TilesInRect(anX, anY, anX + 1, anY + 1, tileList);
  634. if tileList.count > 0 then
  635. Result := PgxHeightTileInfo(tileList.First)
  636. else
  637. Result := nil;
  638. finally
  639. tileList.Free;
  640. end;
  641. end;
  642. function TgxHeightTileFile.XYHeight(anX, anY: Integer): SmallInt;
  643. var
  644. tileInfo: PgxHeightTileInfo;
  645. tile: PHeightTile;
  646. begin
  647. // Current tile per chance?
  648. with FHeightTile.info do
  649. begin
  650. if (left <= anX) and (left + width > anX) and (top <= anY) and
  651. (top + height > anY) then
  652. begin
  653. Result := FHeightTile.data[(anX - left) + (anY - top) * width];
  654. Exit;
  655. end;
  656. end;
  657. // Find corresponding tile if any
  658. tileInfo := XYTileInfo(anX, anY);
  659. if Assigned(tileInfo) then
  660. with tileInfo^ do
  661. begin
  662. tile := GetTile(left, top);
  663. Result := tile.data[(anX - left) + (anY - top) * width];
  664. end
  665. else
  666. Result := DefaultZ;
  667. end;
  668. procedure TgxHeightTileFile.TilesInRect(aLeft, aTop, aRight, aBottom: Integer;
  669. destList: TList);
  670. var
  671. I, n, qx, qy, idx: Integer;
  672. p: PIntegerArray;
  673. tileInfo: PgxHeightTileInfo;
  674. begin
  675. destList.count := 0;
  676. // Clamp to world
  677. if (aLeft > SizeX) or (aRight < 0) or (aTop > SizeY) or (aBottom < 0) then
  678. Exit;
  679. if aLeft < 0 then
  680. aLeft := 0;
  681. if aRight > SizeX then
  682. aRight := SizeX;
  683. if aTop < 0 then
  684. aTop := 0;
  685. if aBottom > SizeY then
  686. aBottom := SizeY;
  687. // Collect tiles on quads
  688. Inc(FLastMark);
  689. for qy := QuadTableY(aTop) to QuadTableY(aBottom) do
  690. begin
  691. for qx := QuadTableX(aLeft) to QuadTableX(aRight) do
  692. begin
  693. n := High(FQuadTable[qx, qy]);
  694. p := @FQuadTable[qx, qy][0];
  695. for I := 0 to n do
  696. begin
  697. idx := p[I];
  698. if FTileMark[idx] <> FLastMark then
  699. begin
  700. FTileMark[idx] := FLastMark;
  701. tileInfo := @FTileIndex[idx];
  702. with tileInfo^ do
  703. begin
  704. if (left <= aRight) and (top <= aBottom) and (aLeft < left + width)
  705. and (aTop < top + height) then
  706. destList.Add(tileInfo);
  707. end;
  708. end;
  709. end;
  710. end;
  711. end;
  712. end;
  713. function TgxHeightTileFile.TileCount: Integer;
  714. begin
  715. Result := Length(FTileIndex);
  716. end;
  717. function TgxHeightTileFile.GetTiles(index: Integer): PgxHeightTileInfo;
  718. begin
  719. Result := @FTileIndex[index];
  720. end;
  721. function TgxHeightTileFile.IndexOfTile(aTile: PgxHeightTileInfo): Integer;
  722. var
  723. c: Cardinal;
  724. begin
  725. c := Cardinal(aTile) - Cardinal(@FTileIndex[0]);
  726. if (c mod SizeOf(TgxHeightTileInfo)) = 0 then
  727. begin
  728. Result := (c div SizeOf(TgxHeightTileInfo));
  729. if (Result < 0) or (Result > High(FTileIndex)) then
  730. Result := -1;
  731. end
  732. else
  733. Result := -1;
  734. end;
  735. function TgxHeightTileFile.TileCompressedSize(tileIndex: Integer): Integer;
  736. begin
  737. if tileIndex < High(FTileIndex) then
  738. Result := FTileIndex[tileIndex + 1].fileOffset - FTileIndex[tileIndex]
  739. .fileOffset
  740. else
  741. Result := TileIndexOffset - FTileIndex[tileIndex].fileOffset;
  742. end;
  743. end.