GLS.TilePlane.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. //
  2. // The multimedia graphics platform GLScene https://github.com/glscene
  3. //
  4. unit GLS.TilePlane;
  5. (* Implements a tiled texture plane. *)
  6. interface
  7. {$I GLScene.inc}
  8. uses
  9. Winapi.OpenGL,
  10. System.Classes,
  11. GLS.OpenGLTokens,
  12. GLS.Scene,
  13. GLS.State,
  14. GLS.VectorGeometry,
  15. GLS.Context,
  16. GLS.Material,
  17. GLS.Objects,
  18. GLS.PersistentClasses,
  19. GLS.VectorLists,
  20. GLS.RenderContextInfo,
  21. GLS.XOpenGL;
  22. type
  23. // Stores row information for a tiled area.
  24. TGLTiledAreaRow = class(TGLPersistentObject)
  25. private
  26. FColMin, FColMax: Integer;
  27. FData: TGLIntegerList;
  28. protected
  29. procedure SetColMin(const val: Integer);
  30. procedure SetColMax(const val: Integer);
  31. function GetCell(col: Integer): Integer;
  32. procedure SetCell(col, val: Integer);
  33. public
  34. constructor Create; override;
  35. destructor Destroy; override;
  36. procedure WriteToFiler(writer: TGLVirtualWriter); override;
  37. procedure ReadFromFiler(reader: TGLVirtualReader); override;
  38. property Cell[col: Integer]: Integer read GetCell write SetCell; default;
  39. property ColMin: Integer read FColMin write SetColMin;
  40. property ColMax: Integer read FColMax write SetColMax;
  41. property Data: TGLIntegerList read FData;
  42. procedure Pack;
  43. function Empty: Boolean;
  44. procedure RemapTiles(remapList: TGLIntegerList);
  45. end;
  46. (* Stores tile information in a tiled area.
  47. Each tile stores an integer value with zero the default value,
  48. assumed as "empty". *)
  49. TGLTiledArea = class(TGLPersistentObject)
  50. private
  51. FRowMin, FRowMax: Integer;
  52. FRows: TGLPersistentObjectList;
  53. protected
  54. procedure SetRowMin(const val: Integer);
  55. procedure SetRowMax(const val: Integer);
  56. function GetTile(col, row: Integer): Integer;
  57. procedure SetTile(col, row, val: Integer);
  58. function GetRow(index: Integer): TGLTiledAreaRow;
  59. public
  60. constructor Create; override;
  61. destructor Destroy; override;
  62. procedure WriteToFiler(writer: TGLVirtualWriter); override;
  63. procedure ReadFromFiler(reader: TGLVirtualReader); override;
  64. property Tile[col, row: Integer]: Integer read GetTile
  65. write SetTile; default;
  66. property Row[index: Integer]: TGLTiledAreaRow read GetRow;
  67. property RowMin: Integer read FRowMin write SetRowMin;
  68. property RowMax: Integer read FRowMax write SetRowMax;
  69. procedure Pack;
  70. procedure Clear;
  71. function Empty: Boolean;
  72. procedure RemapTiles(remapList: TGLIntegerList);
  73. end;
  74. (* A tiled textured plane.
  75. This plane object stores and displays texture tiles that composes it,
  76. and is optimized to minimize texture switches when rendering.
  77. Its bounding dimensions are determined by its painted tile. *)
  78. TGLTilePlane = class(TGLImmaterialSceneObject)
  79. private
  80. FNoZWrite: Boolean;
  81. FTiles: TGLTiledArea;
  82. FMaterialLibrary: TGLMaterialLibrary;
  83. FSortByMaterials: Boolean;
  84. protected
  85. procedure SetNoZWrite(const val: Boolean);
  86. procedure SetTiles(const val: TGLTiledArea);
  87. procedure SetMaterialLibrary(const val: TGLMaterialLibrary);
  88. procedure SetSortByMaterials(const val: Boolean);
  89. procedure Notification(AComponent: TComponent;
  90. Operation: TOperation); override;
  91. public
  92. constructor Create(AOwner: TComponent); override;
  93. destructor Destroy; override;
  94. procedure DoRender(var ARci: TGLRenderContextInfo;
  95. ARenderSelf, ARenderChildren: Boolean); override;
  96. procedure BuildList(var rci: TGLRenderContextInfo); override;
  97. // Access to the TiledArea data
  98. property Tiles: TGLTiledArea read FTiles write SetTiles;
  99. (* Controls the sorting of tiles by material.
  100. This property should ideally be left always at its default, True,
  101. except for debugging and performance measurement, which is why
  102. it's only public and not published. *)
  103. property SortByMaterials: Boolean read FSortByMaterials
  104. write SetSortByMaterials;
  105. published
  106. // If True the tiles are rendered without writing to the ZBuffer.
  107. property NoZWrite: Boolean read FNoZWrite write SetNoZWrite;
  108. (* Material library where tiles materials will be stored/retrieved.
  109. The lower 16 bits of the tile integer value is understood as being
  110. the index of the tile's material in the library (material of
  111. index zero is thus unused). *)
  112. property MaterialLibrary: TGLMaterialLibrary read FMaterialLibrary
  113. write SetMaterialLibrary;
  114. end;
  115. // -------------------------------------------------------------
  116. implementation
  117. // -------------------------------------------------------------
  118. // ------------------
  119. // ------------------ TGLTiledAreaRow ------------------
  120. // ------------------
  121. constructor TGLTiledAreaRow.Create;
  122. begin
  123. inherited;
  124. FData := TGLIntegerList.Create;
  125. FColMin := 0;
  126. FColMax := -1;
  127. end;
  128. destructor TGLTiledAreaRow.Destroy;
  129. begin
  130. FData.Free;
  131. inherited;
  132. end;
  133. procedure TGLTiledAreaRow.WriteToFiler(writer: TGLVirtualWriter);
  134. begin
  135. inherited WriteToFiler(writer);
  136. with writer do
  137. begin
  138. WriteInteger(0); // Archive Version 0
  139. WriteInteger(FColMin);
  140. FData.WriteToFiler(writer);
  141. end;
  142. end;
  143. procedure TGLTiledAreaRow.ReadFromFiler(reader: TGLVirtualReader);
  144. var
  145. archiveVersion: Integer;
  146. begin
  147. inherited ReadFromFiler(reader);
  148. archiveVersion := reader.ReadInteger;
  149. if archiveVersion = 0 then
  150. with reader do
  151. begin
  152. FColMin := ReadInteger;
  153. FData.ReadFromFiler(reader);
  154. FColMax := FColMin + FData.Count - 1;
  155. end;
  156. end;
  157. procedure TGLTiledAreaRow.Pack;
  158. var
  159. i, startSkip: Integer;
  160. begin
  161. startSkip := MaxInt;
  162. for i := 0 to FData.Count - 1 do
  163. begin
  164. if FData.List^[i] <> 0 then
  165. begin
  166. startSkip := i;
  167. Break;
  168. end;
  169. end;
  170. if startSkip = MaxInt then
  171. begin
  172. FData.Clear;
  173. FColMax := ColMin - 1;
  174. end
  175. else
  176. begin
  177. for i := FData.Count - 1 downto 0 do
  178. begin
  179. if FData.List^[i] <> 0 then
  180. begin
  181. FData.Count := i + 1;
  182. FColMax := FColMin + FData.Count - 1;
  183. Break;
  184. end;
  185. end;
  186. if startSkip > 0 then
  187. begin
  188. FData.DeleteItems(0, startSkip);
  189. FColMin := FColMin + startSkip;
  190. end;
  191. end;
  192. end;
  193. function TGLTiledAreaRow.Empty: Boolean;
  194. begin
  195. Result := (FData.Count = 0);
  196. end;
  197. procedure TGLTiledAreaRow.RemapTiles(remapList: TGLIntegerList);
  198. var
  199. i, k: Integer;
  200. begin
  201. for i := 0 to FData.Count - 1 do
  202. begin
  203. k := FData[i];
  204. if Cardinal(k) < Cardinal(remapList.Count) then
  205. FData[i] := remapList[k]
  206. else
  207. FData[i] := 0;
  208. end;
  209. end;
  210. procedure TGLTiledAreaRow.SetColMax(const val: Integer);
  211. begin
  212. if val >= ColMin then
  213. FData.Count := val - ColMin + 1
  214. else
  215. FData.Clear;
  216. FColMax := val;
  217. end;
  218. procedure TGLTiledAreaRow.SetColMin(const val: Integer);
  219. begin
  220. if ColMax >= val then
  221. begin
  222. if val < ColMin then
  223. FData.InsertNulls(0, ColMin - val)
  224. else
  225. FData.DeleteItems(0, val - ColMin);
  226. end
  227. else
  228. FData.Clear;
  229. FColMin := val;
  230. end;
  231. function TGLTiledAreaRow.GetCell(col: Integer): Integer;
  232. begin
  233. if (col >= ColMin) and (col <= ColMax) then
  234. Result := FData[col - ColMin]
  235. else
  236. Result := 0;
  237. end;
  238. procedure TGLTiledAreaRow.SetCell(col, val: Integer);
  239. var
  240. i: Integer;
  241. begin
  242. i := col - ColMin;
  243. if Cardinal(i) >= Cardinal(FData.Count) then
  244. begin
  245. if ColMin <= ColMax then
  246. begin
  247. if col < ColMin then
  248. ColMin := col;
  249. if col > ColMax then
  250. ColMax := col;
  251. end
  252. else
  253. begin
  254. FColMin := col;
  255. FColMax := col;
  256. FData.Add(val);
  257. Exit;
  258. end;
  259. end;
  260. FData[col - ColMin] := val;
  261. end;
  262. // ------------------
  263. // ------------------ TGLTiledArea ------------------
  264. // ------------------
  265. constructor TGLTiledArea.Create;
  266. begin
  267. inherited;
  268. FRows := TGLPersistentObjectList.Create;
  269. FRowMax := -1;
  270. end;
  271. destructor TGLTiledArea.Destroy;
  272. begin
  273. FRows.CleanFree;
  274. inherited;
  275. end;
  276. procedure TGLTiledArea.WriteToFiler(writer: TGLVirtualWriter);
  277. begin
  278. inherited WriteToFiler(writer);
  279. with writer do
  280. begin
  281. WriteInteger(0); // Archive Version 0
  282. WriteInteger(FRowMin);
  283. FRows.WriteToFiler(writer);
  284. end;
  285. end;
  286. procedure TGLTiledArea.ReadFromFiler(reader: TGLVirtualReader);
  287. var
  288. archiveVersion: Integer;
  289. begin
  290. inherited ReadFromFiler(reader);
  291. archiveVersion := reader.ReadInteger;
  292. if archiveVersion = 0 then
  293. with reader do
  294. begin
  295. FRowMin := ReadInteger;
  296. FRows.ReadFromFiler(reader);
  297. FRowMax := FRowMin + FRows.Count - 1;
  298. end;
  299. end;
  300. procedure TGLTiledArea.Pack;
  301. var
  302. i, firstNonNil, lastNonNil: Integer;
  303. r: TGLTiledAreaRow;
  304. begin
  305. // pack all rows, free empty ones, determine 1st and last non-nil
  306. lastNonNil := -1;
  307. firstNonNil := FRows.Count;
  308. for i := 0 to FRows.Count - 1 do
  309. begin
  310. r := TGLTiledAreaRow(FRows.List^[i]);
  311. if Assigned(r) then
  312. begin
  313. r.Pack;
  314. if r.FData.Count = 0 then
  315. begin
  316. r.Free;
  317. FRows.List^[i] := nil;
  318. end;
  319. end;
  320. if Assigned(r) then
  321. begin
  322. lastNonNil := i;
  323. if i < firstNonNil then
  324. firstNonNil := i;
  325. end;
  326. end;
  327. if lastNonNil >= 0 then
  328. begin
  329. FRows.Count := lastNonNil + 1;
  330. FRowMax := FRowMin + FRows.Count - 1;
  331. if firstNonNil > 0 then
  332. begin
  333. FRowMin := FRowMin + firstNonNil;
  334. FRows.DeleteItems(0, firstNonNil);
  335. end;
  336. end
  337. else
  338. FRows.Clear;
  339. end;
  340. procedure TGLTiledArea.Clear;
  341. begin
  342. FRows.Clean;
  343. FRowMin := 0;
  344. FRowMax := -1;
  345. end;
  346. function TGLTiledArea.Empty: Boolean;
  347. begin
  348. Result := (FRows.Count = 0);
  349. end;
  350. procedure TGLTiledArea.RemapTiles(remapList: TGLIntegerList);
  351. var
  352. i: Integer;
  353. r: TGLTiledAreaRow;
  354. begin
  355. for i := 0 to FRows.Count - 1 do
  356. begin
  357. r := TGLTiledAreaRow(FRows[i]);
  358. if Assigned(r) then
  359. r.RemapTiles(remapList);
  360. end;
  361. end;
  362. function TGLTiledArea.GetTile(col, row: Integer): Integer;
  363. var
  364. i: Integer;
  365. r: TGLTiledAreaRow;
  366. begin
  367. i := row - RowMin;
  368. if Cardinal(i) < Cardinal(FRows.Count) then
  369. begin
  370. r := TGLTiledAreaRow(FRows[row - RowMin]);
  371. if Assigned(r) then
  372. Result := r.Cell[col]
  373. else
  374. Result := 0;
  375. end
  376. else
  377. Result := 0;
  378. end;
  379. procedure TGLTiledArea.SetTile(col, row, val: Integer);
  380. var
  381. r: TGLTiledAreaRow;
  382. begin
  383. if row < RowMin then
  384. RowMin := row;
  385. if row > RowMax then
  386. RowMax := row;
  387. r := TGLTiledAreaRow(FRows[row - RowMin]);
  388. if not Assigned(r) then
  389. begin
  390. r := TGLTiledAreaRow.Create;
  391. FRows[row - RowMin] := r;
  392. end;
  393. r.Cell[col] := val;
  394. end;
  395. function TGLTiledArea.GetRow(index: Integer): TGLTiledAreaRow;
  396. begin
  397. index := index - RowMin;
  398. if Cardinal(index) < Cardinal(FRows.Count) then
  399. Result := TGLTiledAreaRow(FRows[index])
  400. else
  401. Result := nil;
  402. end;
  403. procedure TGLTiledArea.SetRowMax(const val: Integer);
  404. begin
  405. if val >= RowMin then
  406. begin
  407. if val > RowMax then
  408. FRows.AddNils(val - RowMax)
  409. else
  410. FRows.DeleteAndFreeItems(val - RowMin + 1, FRows.Count);
  411. end
  412. else
  413. FRows.Clean;
  414. FRowMax := val;
  415. end;
  416. procedure TGLTiledArea.SetRowMin(const val: Integer);
  417. begin
  418. if val <= RowMax then
  419. begin
  420. if val < RowMin then
  421. FRows.InsertNils(0, RowMin - val)
  422. else
  423. FRows.DeleteAndFreeItems(0, val - RowMin);
  424. end
  425. else
  426. FRows.Clean;
  427. FRowMin := val;
  428. end;
  429. // ------------------
  430. // ------------------ TGLTilePlane ------------------
  431. // ------------------
  432. constructor TGLTilePlane.Create(AOwner: TComponent);
  433. begin
  434. inherited Create(AOwner);
  435. FTiles := TGLTiledArea.Create;
  436. FSortByMaterials := True;
  437. end;
  438. destructor TGLTilePlane.Destroy;
  439. begin
  440. MaterialLibrary := nil;
  441. FTiles.Free;
  442. inherited;
  443. end;
  444. procedure TGLTilePlane.SetNoZWrite(const val: Boolean);
  445. begin
  446. if FNoZWrite <> val then
  447. begin
  448. FNoZWrite := val;
  449. StructureChanged;
  450. end;
  451. end;
  452. procedure TGLTilePlane.SetTiles(const val: TGLTiledArea);
  453. begin
  454. if val <> FTiles then
  455. begin
  456. FTiles.Assign(val);
  457. StructureChanged;
  458. end;
  459. end;
  460. procedure TGLTilePlane.SetMaterialLibrary(const val: TGLMaterialLibrary);
  461. begin
  462. if FMaterialLibrary <> val then
  463. begin
  464. if Assigned(FMaterialLibrary) then
  465. begin
  466. DestroyHandle;
  467. FMaterialLibrary.RemoveFreeNotification(Self);
  468. end;
  469. FMaterialLibrary := val;
  470. if Assigned(FMaterialLibrary) then
  471. FMaterialLibrary.FreeNotification(Self);
  472. StructureChanged;
  473. end;
  474. end;
  475. procedure TGLTilePlane.SetSortByMaterials(const val: Boolean);
  476. begin
  477. FSortByMaterials := val;
  478. StructureChanged;
  479. end;
  480. procedure TGLTilePlane.Notification(AComponent: TComponent;
  481. Operation: TOperation);
  482. begin
  483. if Operation = opRemove then
  484. begin
  485. if AComponent = FMaterialLibrary then
  486. MaterialLibrary := nil;
  487. end;
  488. inherited;
  489. end;
  490. procedure TGLTilePlane.DoRender(var ARci: TGLRenderContextInfo;
  491. ARenderSelf, ARenderChildren: Boolean);
  492. var
  493. i: Integer;
  494. begin
  495. if (not ListHandleAllocated) and Assigned(FMaterialLibrary) then
  496. begin
  497. for i := 0 to MaterialLibrary.Materials.Count - 1 do
  498. MaterialLibrary.Materials[i].PrepareBuildList;
  499. end;
  500. inherited;
  501. end;
  502. procedure TGLTilePlane.BuildList(var rci: TGLRenderContextInfo);
  503. type
  504. TQuadListInfo = packed record
  505. x, y: TGLIntegerList;
  506. end;
  507. procedure IssueQuad(col, row: Integer);
  508. begin
  509. xgl.TexCoord2f(col, row);
  510. gl.Vertex2f(col, row);
  511. xgl.TexCoord2f(col + 1, row);
  512. gl.Vertex2f(col + 1, row);
  513. xgl.TexCoord2f(col + 1, row + 1);
  514. gl.Vertex2f(col + 1, row + 1);
  515. xgl.TexCoord2f(col, row + 1);
  516. gl.Vertex2f(col, row + 1);
  517. end;
  518. var
  519. i, j, row, col, t: Integer;
  520. r: TGLTiledAreaRow;
  521. libMat: TGLLibMaterial;
  522. quadInfos: array of TQuadListInfo;
  523. begin
  524. if MaterialLibrary = nil then
  525. Exit;
  526. // initialize infos
  527. gl.Normal3fv(@ZVector);
  528. if FNoZWrite then
  529. rci.GLStates.DepthWriteMask := False;
  530. if SortByMaterials then
  531. begin
  532. SetLength(quadInfos, MaterialLibrary.Materials.Count);
  533. for i := 0 to High(quadInfos) do
  534. begin // correction in (i:=0) from (i:=1)
  535. quadInfos[i].x := TGLIntegerList.Create;
  536. quadInfos[i].y := TGLIntegerList.Create;
  537. end;
  538. // collect quads into quadInfos, sorted by material
  539. for row := Tiles.RowMin to Tiles.RowMax do
  540. begin
  541. r := Tiles.row[row];
  542. if Assigned(r) then
  543. begin
  544. for col := r.ColMin to r.ColMax do
  545. begin
  546. t := r.Cell[col] and $FFFF;
  547. if (t > -1) and (t < MaterialLibrary.Materials.Count) then
  548. begin // correction in (t>-1) from (t>0)
  549. quadInfos[t].x.Add(col);
  550. quadInfos[t].y.Add(row);
  551. end;
  552. end;
  553. end;
  554. end;
  555. // render and cleanup
  556. for i := 0 to High(quadInfos) do
  557. begin // correction in (i:=0) from (i:=1)
  558. if quadInfos[i].x.Count > 0 then
  559. begin
  560. libMat := MaterialLibrary.Materials[i];
  561. libMat.Apply(rci);
  562. repeat
  563. gl.Begin_(GL_QUADS);
  564. with quadInfos[i] do
  565. for j := 0 to x.Count - 1 do
  566. IssueQuad(x[j], y[j]);
  567. gl.End_;
  568. until not libMat.UnApply(rci);
  569. end;
  570. quadInfos[i].x.Free;
  571. quadInfos[i].y.Free;
  572. end;
  573. end
  574. else
  575. begin
  576. // process all quads in order
  577. for row := Tiles.RowMin to Tiles.RowMax do
  578. begin
  579. r := Tiles.row[row];
  580. if Assigned(r) then
  581. begin
  582. for col := r.ColMin to r.ColMax do
  583. begin
  584. t := r.Cell[col] and $FFFF;
  585. if (t > -1) and (t < MaterialLibrary.Materials.Count) then
  586. begin // correction in (t>-1) from (t>0)
  587. libMat := MaterialLibrary.Materials[t];
  588. libMat.Apply(rci);
  589. repeat
  590. gl.Begin_(GL_QUADS);
  591. IssueQuad(col, row);
  592. gl.End_;
  593. until not libMat.UnApply(rci);
  594. end;
  595. end;
  596. end;
  597. end;
  598. end;
  599. if FNoZWrite then
  600. rci.GLStates.DepthWriteMask := True;
  601. end;
  602. // -------------------------------------------------------------
  603. initialization
  604. // -------------------------------------------------------------
  605. RegisterClasses([TGLTilePlane, TGLTiledAreaRow, TGLTiledArea]);
  606. end.