GLS.TilePlane.pas 16 KB

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