GXS.Silhouette.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. //
  2. // The graphics engine GXScene https://github.com/glscene
  3. //
  4. unit GXS.Silhouette;
  5. (*
  6. Enhanced silhouette classes.
  7. Introduces more evolved/specific silhouette generation and management
  8. classes.
  9. CAUTION : both connectivity classes leak memory.
  10. *)
  11. interface
  12. {$I Stage.Defines.inc}
  13. uses
  14. System.Classes,
  15. System.SysUtils,
  16. Stage.VectorTypes,
  17. Stage.VectorGeometry,
  18. GXS.VectorLists;
  19. type
  20. TgxSilhouetteStyle = (ssOmni, ssParallel);
  21. (* Silouhette generation parameters.
  22. SeenFrom and LightDirection are expected in local coordinates. *)
  23. TgxSilhouetteParameters = packed record
  24. SeenFrom, LightDirection: TAffineVector;
  25. Style: TgxSilhouetteStyle;
  26. CappingRequired: Boolean;
  27. end;
  28. (* Base class storing a volume silhouette.
  29. Made of a set of indexed vertices defining an outline, and another set
  30. of indexed vertices defining a capping volume. Coordinates system
  31. is the object's unscaled local coordinates system.
  32. This is the base class, you can use the TgxSilhouette subclass if you
  33. need some helper methods for generating the indexed sets. *)
  34. TgxSilhouette = class
  35. private
  36. FVertices: TgxVectorList;
  37. FIndices: TgxIntegerList;
  38. FCapIndices: TgxIntegerList;
  39. FParameters: TgxSilhouetteParameters;
  40. protected
  41. procedure SetIndices(const value: TgxIntegerList);
  42. procedure SetCapIndices(const value: TgxIntegerList);
  43. procedure SetVertices(const value: TgxVectorList);
  44. public
  45. constructor Create; virtual;
  46. destructor Destroy; override;
  47. property Parameters: TgxSilhouetteParameters read FParameters write FParameters;
  48. property Vertices: TgxVectorList read FVertices write SetVertices;
  49. property Indices: TgxIntegerList read FIndices write SetIndices;
  50. property CapIndices: TgxIntegerList read FCapIndices write SetCapIndices;
  51. procedure Flush;
  52. procedure Clear;
  53. procedure ExtrudeVerticesToInfinity(const origin: TAffineVector);
  54. (* Adds an edge (two vertices) to the silhouette.
  55. If TightButSlow is true, no vertices will be doubled in the
  56. silhouette list. This should only be used when creating re-usable
  57. silhouettes, because it's much slower. *)
  58. procedure AddEdgeToSilhouette(const v0, v1: TAffineVector; tightButSlow: Boolean);
  59. procedure AddIndexedEdgeToSilhouette(const Vi0, Vi1: integer);
  60. (* Adds a capping triangle to the silhouette.
  61. If TightButSlow is true, no vertices will be doubled in the
  62. silhouette list. This should only be used when creating re-usable
  63. silhouettes, because it's much slower. *)
  64. procedure AddCapToSilhouette(const v0, v1, v2: TAffineVector; tightButSlow: Boolean);
  65. procedure AddIndexedCapToSilhouette(const Vi0, Vi1, vi2: integer);
  66. end;
  67. TBaseConnectivity = class
  68. protected
  69. FPrecomputeFaceNormal: Boolean;
  70. function GetEdgeCount: integer; virtual;
  71. function GetFaceCount: integer; virtual;
  72. public
  73. property EdgeCount: integer read GetEdgeCount;
  74. property FaceCount: integer read GetFaceCount;
  75. property PrecomputeFaceNormal: Boolean read FPrecomputeFaceNormal;
  76. procedure CreateSilhouette(const ASilhouetteParameters: TgxSilhouetteParameters; var ASilhouette: TgxSilhouette;
  77. AddToSilhouette: Boolean); virtual;
  78. constructor Create(APrecomputeFaceNormal: Boolean); virtual;
  79. end;
  80. TConnectivity = class(TBaseConnectivity)
  81. protected
  82. (* All storage of faces and adges are cut up into tiny pieces for a reason,
  83. it'd be nicer with Structs or classes, but it's actually faster this way.
  84. The reason it's faster is because of less cache overwrites when we only
  85. access a tiny bit of a triangle (for instance), not all data. *)
  86. FEdgeVertices: TgxIntegerList;
  87. FEdgeFaces: TgxIntegerList;
  88. FFaceVisible: TGByteList;
  89. FFaceVertexIndex: TgxIntegerList;
  90. FFaceNormal: TgxAffineVectorList;
  91. FVertexMemory: TgxIntegerList;
  92. FVertices: TgxAffineVectorList;
  93. function GetEdgeCount: integer; override;
  94. function GetFaceCount: integer; override;
  95. function ReuseOrFindVertexID(const SeenFrom: TAffineVector; ASilhouette: TgxSilhouette; index: integer): integer;
  96. public
  97. // Clears out all connectivity information.
  98. procedure Clear; virtual;
  99. procedure CreateSilhouette(const silhouetteParameters: TgxSilhouetteParameters; var ASilhouette: TgxSilhouette;
  100. AddToSilhouette: Boolean); override;
  101. function AddIndexedEdge(vertexIndex0, vertexIndex1: integer; FaceID: integer): integer;
  102. function AddIndexedFace(Vi0, Vi1, vi2: integer): integer;
  103. function AddFace(const vertex0, vertex1, vertex2: TAffineVector): integer;
  104. function AddQuad(const vertex0, vertex1, vertex2, vertex3: TAffineVector): integer;
  105. property EdgeCount: integer read GetEdgeCount;
  106. property FaceCount: integer read GetFaceCount;
  107. constructor Create(APrecomputeFaceNormal: Boolean); override;
  108. destructor Destroy; override;
  109. end;
  110. implementation // -------------------------------------------------------------
  111. // ------------------
  112. // ------------------ TgxSilhouette ------------------
  113. // ------------------
  114. constructor TgxSilhouette.Create;
  115. begin
  116. inherited;
  117. FVertices := TgxVectorList.Create;
  118. FIndices := TgxIntegerList.Create;
  119. FCapIndices := TgxIntegerList.Create;
  120. end;
  121. destructor TgxSilhouette.Destroy;
  122. begin
  123. FCapIndices.Free;
  124. FIndices.Free;
  125. FVertices.Free;
  126. inherited;
  127. end;
  128. procedure TgxSilhouette.SetIndices(const value: TgxIntegerList);
  129. begin
  130. FIndices.Assign(value);
  131. end;
  132. procedure TgxSilhouette.SetCapIndices(const value: TgxIntegerList);
  133. begin
  134. FCapIndices.Assign(value);
  135. end;
  136. procedure TgxSilhouette.SetVertices(const value: TgxVectorList);
  137. begin
  138. FVertices.Assign(value);
  139. end;
  140. procedure TgxSilhouette.Flush;
  141. begin
  142. FVertices.Flush;
  143. FIndices.Flush;
  144. FCapIndices.Flush;
  145. end;
  146. procedure TgxSilhouette.Clear;
  147. begin
  148. FVertices.Clear;
  149. FIndices.Clear;
  150. FCapIndices.Clear;
  151. end;
  152. procedure TgxSilhouette.ExtrudeVerticesToInfinity(const origin: TAffineVector);
  153. var
  154. i, nv, ni, nc, k: integer;
  155. vList, vListN: PVectorArray;
  156. iList, iList2: PIntegerArray;
  157. begin
  158. // extrude vertices
  159. nv := Vertices.Count;
  160. Vertices.Count := 2 * nv;
  161. vList := Vertices.List;
  162. vListN := @vList[nv];
  163. for i := 0 to nv - 1 do
  164. begin
  165. vListN^[i].W := 0;
  166. VectorSubtract(PAffineVector(@vList[i])^, origin, PAffineVector(@vListN[i])^);
  167. end;
  168. // change silhouette indices to quad indices
  169. ni := Indices.Count;
  170. Indices.Count := 2 * ni;
  171. iList := Indices.List;
  172. i := ni - 2;
  173. while i >= 0 do
  174. begin
  175. iList2 := @iList^[2 * i];
  176. iList2^[0] := iList^[i];
  177. iList2^[1] := iList^[i + 1];
  178. iList2^[2] := iList^[i + 1] + nv;
  179. iList2^[3] := iList^[i] + nv;
  180. Dec(i, 2);
  181. end;
  182. // add extruded triangles to capIndices
  183. nc := CapIndices.Count;
  184. CapIndices.Capacity := 2 * nc;
  185. iList := CapIndices.List;
  186. for i := nc - 1 downto 0 do
  187. begin
  188. k := iList^[i];
  189. CapIndices.Add(k);
  190. iList^[i] := k + nv;
  191. end;
  192. end;
  193. // ------------------
  194. // ------------------ TgxSilhouette ------------------
  195. // ------------------
  196. procedure TgxSilhouette.AddEdgeToSilhouette(const v0, v1: TAffineVector; tightButSlow: Boolean);
  197. begin
  198. if tightButSlow then
  199. Indices.Add(Vertices.FindOrAddPoint(v0), Vertices.FindOrAddPoint(v1))
  200. else
  201. Indices.Add(Vertices.Add(v0, 1), Vertices.Add(v1, 1));
  202. end;
  203. procedure TgxSilhouette.AddIndexedEdgeToSilhouette(const Vi0, Vi1: integer);
  204. begin
  205. Indices.Add(Vi0, Vi1);
  206. end;
  207. procedure TgxSilhouette.AddCapToSilhouette(const v0, v1, v2: TAffineVector; tightButSlow: Boolean);
  208. begin
  209. if tightButSlow then
  210. CapIndices.Add(Vertices.FindOrAddPoint(v0), Vertices.FindOrAddPoint(v1), Vertices.FindOrAddPoint(v2))
  211. else
  212. CapIndices.Add(Vertices.Add(v0, 1), Vertices.Add(v1, 1), Vertices.Add(v2, 1));
  213. end;
  214. procedure TgxSilhouette.AddIndexedCapToSilhouette(const Vi0, Vi1, vi2: integer);
  215. begin
  216. CapIndices.Add(Vi0, Vi1, vi2);
  217. end;
  218. // ------------------
  219. // ------------------ TBaseConnectivity ------------------
  220. // ------------------
  221. constructor TBaseConnectivity.Create(APrecomputeFaceNormal: Boolean);
  222. begin
  223. FPrecomputeFaceNormal := APrecomputeFaceNormal;
  224. end;
  225. procedure TBaseConnectivity.CreateSilhouette(const ASilhouetteParameters: TgxSilhouetteParameters;
  226. var ASilhouette: TgxSilhouette; AddToSilhouette: Boolean);
  227. begin
  228. // Purely virtual!
  229. end;
  230. // ------------------
  231. // ------------------ TConnectivity ------------------
  232. // ------------------
  233. function TBaseConnectivity.GetEdgeCount: integer;
  234. begin
  235. result := 0;
  236. end;
  237. function TBaseConnectivity.GetFaceCount: integer;
  238. begin
  239. result := 0;
  240. end;
  241. { TConnectivity }
  242. constructor TConnectivity.Create(APrecomputeFaceNormal: Boolean);
  243. begin
  244. FFaceVisible := TGByteList.Create;
  245. FFaceVertexIndex := TgxIntegerList.Create;
  246. FFaceNormal := TgxAffineVectorList.Create;
  247. FEdgeVertices := TgxIntegerList.Create;
  248. FEdgeFaces := TgxIntegerList.Create;
  249. FPrecomputeFaceNormal := APrecomputeFaceNormal;
  250. FVertexMemory := TgxIntegerList.Create;
  251. FVertices := TgxAffineVectorList.Create;
  252. end;
  253. destructor TConnectivity.Destroy;
  254. begin
  255. Clear;
  256. FFaceVisible.Free;
  257. FFaceVertexIndex.Free;
  258. FFaceNormal.Free;
  259. FEdgeVertices.Free;
  260. FEdgeFaces.Free;
  261. FVertexMemory.Free;
  262. if Assigned(FVertices) then
  263. FVertices.Free;
  264. inherited;
  265. end;
  266. procedure TConnectivity.Clear;
  267. begin
  268. FEdgeVertices.Clear;
  269. FEdgeFaces.Clear;
  270. FFaceVisible.Clear;
  271. FFaceVertexIndex.Clear;
  272. FFaceNormal.Clear;
  273. FVertexMemory.Clear;
  274. if FVertices <> nil then
  275. FVertices.Clear;
  276. end;
  277. procedure TConnectivity.CreateSilhouette(const silhouetteParameters: TgxSilhouetteParameters; var ASilhouette: TgxSilhouette;
  278. AddToSilhouette: Boolean);
  279. var
  280. i: integer;
  281. vis: PIntegerArray;
  282. tVi0, tVi1: integer;
  283. faceNormal: TAffineVector;
  284. face0ID, face1ID: integer;
  285. faceIsVisible: Boolean;
  286. verticesList: PAffineVectorArray;
  287. begin
  288. if not Assigned(ASilhouette) then
  289. ASilhouette := TgxSilhouette.Create
  290. else if not AddToSilhouette then
  291. ASilhouette.Flush;
  292. // Clear the vertex memory
  293. FVertexMemory.Flush;
  294. // Update visibility information for all Faces
  295. vis := FFaceVertexIndex.List;
  296. for i := 0 to FaceCount - 1 do
  297. begin
  298. if FPrecomputeFaceNormal then
  299. faceIsVisible := (PointProject(silhouetteParameters.SeenFrom, FVertices.List^[vis^[0]], FFaceNormal.List^[i]) >= 0)
  300. else
  301. begin
  302. verticesList := FVertices.List;
  303. faceNormal := CalcPlaneNormal(verticesList^[vis^[0]], verticesList^[vis^[1]], verticesList^[vis^[2]]);
  304. faceIsVisible := (PointProject(silhouetteParameters.SeenFrom, FVertices.List^[vis^[0]], faceNormal) >= 0);
  305. end;
  306. FFaceVisible[i] := Byte(faceIsVisible);
  307. if (not faceIsVisible) and silhouetteParameters.CappingRequired then
  308. ASilhouette.CapIndices.Add(ReuseOrFindVertexID(silhouetteParameters.SeenFrom, ASilhouette, vis^[0]),
  309. ReuseOrFindVertexID(silhouetteParameters.SeenFrom, ASilhouette, vis^[1]),
  310. ReuseOrFindVertexID(silhouetteParameters.SeenFrom, ASilhouette, vis^[2]));
  311. vis := @vis[3];
  312. end;
  313. for i := 0 to EdgeCount - 1 do
  314. begin
  315. face0ID := FEdgeFaces[i * 2 + 0];
  316. face1ID := FEdgeFaces[i * 2 + 1];
  317. if (face1ID = -1) or (FFaceVisible.List^[face0ID] <> FFaceVisible.List^[face1ID]) then
  318. begin
  319. // Retrieve the two vertice values add add them to the Silhouette list
  320. vis := @FEdgeVertices.List[i * 2];
  321. // In this moment, we _know_ what vertex id the vertex had in the old
  322. // mesh. We can remember this information and re-use it for a speedup
  323. if FFaceVisible.List^[face0ID] = 0 then
  324. begin
  325. tVi0 := ReuseOrFindVertexID(silhouetteParameters.SeenFrom, ASilhouette, vis^[0]);
  326. tVi1 := ReuseOrFindVertexID(silhouetteParameters.SeenFrom, ASilhouette, vis^[1]);
  327. ASilhouette.Indices.Add(tVi0, tVi1);
  328. end
  329. else if face1ID > -1 then
  330. begin
  331. tVi0 := ReuseOrFindVertexID(silhouetteParameters.SeenFrom, ASilhouette, vis^[0]);
  332. tVi1 := ReuseOrFindVertexID(silhouetteParameters.SeenFrom, ASilhouette, vis^[1]);
  333. ASilhouette.Indices.Add(tVi1, tVi0);
  334. end;
  335. end;
  336. end;
  337. end;
  338. function TConnectivity.GetEdgeCount: integer;
  339. begin
  340. result := FEdgeVertices.Count div 2;
  341. end;
  342. function TConnectivity.GetFaceCount: integer;
  343. begin
  344. result := FFaceVisible.Count;
  345. end;
  346. function TConnectivity.ReuseOrFindVertexID(const SeenFrom: TAffineVector; ASilhouette: TgxSilhouette; index: integer): integer;
  347. var
  348. pMemIndex: PInteger;
  349. memIndex, i: integer;
  350. oldCount: integer;
  351. List: PIntegerArray;
  352. begin
  353. if index >= FVertexMemory.Count then
  354. begin
  355. oldCount := FVertexMemory.Count;
  356. FVertexMemory.Count := index + 1;
  357. List := FVertexMemory.List;
  358. for i := oldCount to FVertexMemory.Count - 1 do
  359. List^[i] := -1;
  360. end;
  361. pMemIndex := @FVertexMemory.List[index];
  362. if pMemIndex^ = -1 then
  363. begin
  364. // Add the "near" vertex
  365. memIndex := ASilhouette.Vertices.Add(FVertices.List^[index], 1);
  366. pMemIndex^ := memIndex;
  367. result := memIndex;
  368. end
  369. else
  370. result := pMemIndex^;
  371. end;
  372. function TConnectivity.AddIndexedEdge(vertexIndex0, vertexIndex1: integer; FaceID: integer): integer;
  373. var
  374. i: integer;
  375. edgesVertices: PIntegerArray;
  376. begin
  377. // Make sure that the edge doesn't already exists
  378. edgesVertices := FEdgeVertices.List;
  379. for i := 0 to EdgeCount - 1 do
  380. begin
  381. // Retrieve the two vertices in the edge
  382. if ((edgesVertices^[0] = vertexIndex0) and (edgesVertices^[1] = vertexIndex1)) or
  383. ((edgesVertices^[0] = vertexIndex1) and (edgesVertices^[1] = vertexIndex0)) then
  384. begin
  385. // Update the second Face of the edge and we're done (this _MAY_
  386. // overwrite a previous Face in a broken mesh)
  387. FEdgeFaces[i * 2 + 1] := FaceID;
  388. result := i * 2 + 1;
  389. Exit;
  390. end;
  391. edgesVertices := @edgesVertices[2];
  392. end;
  393. // No edge was found, create a new one
  394. FEdgeVertices.Add(vertexIndex0, vertexIndex1);
  395. FEdgeFaces.Add(FaceID, -1);
  396. result := EdgeCount - 1;
  397. end;
  398. function TConnectivity.AddIndexedFace(Vi0, Vi1, vi2: integer): integer;
  399. var
  400. FaceID: integer;
  401. begin
  402. FFaceVertexIndex.Add(Vi0, Vi1, vi2);
  403. if FPrecomputeFaceNormal then
  404. FFaceNormal.Add(CalcPlaneNormal(FVertices.List^[Vi0], FVertices.List^[Vi1], FVertices.List^[vi2]));
  405. FaceID := FFaceVisible.Add(0);
  406. AddIndexedEdge(Vi0, Vi1, FaceID);
  407. AddIndexedEdge(Vi1, vi2, FaceID);
  408. AddIndexedEdge(vi2, Vi0, FaceID);
  409. Result := FaceID;
  410. end;
  411. function TConnectivity.AddFace(const vertex0, vertex1, vertex2: TAffineVector): integer;
  412. var
  413. Vi0, Vi1, vi2: integer;
  414. begin
  415. Vi0 := FVertices.FindOrAdd(vertex0);
  416. Vi1 := FVertices.FindOrAdd(vertex1);
  417. vi2 := FVertices.FindOrAdd(vertex2);
  418. Result := AddIndexedFace(Vi0, Vi1, vi2);
  419. end;
  420. function TConnectivity.AddQuad(const vertex0, vertex1, vertex2, vertex3: TAffineVector): integer;
  421. var
  422. Vi0, Vi1, vi2, Vi3: integer;
  423. begin
  424. Vi0 := FVertices.FindOrAdd(vertex0);
  425. Vi1 := FVertices.FindOrAdd(vertex1);
  426. vi2 := FVertices.FindOrAdd(vertex2);
  427. Vi3 := FVertices.FindOrAdd(vertex3);
  428. // First face
  429. result := AddIndexedFace(Vi0, Vi1, vi2);
  430. // Second face
  431. AddIndexedFace(vi2, Vi3, Vi0);
  432. end;
  433. end.