2
0

GXS.Nodes.pas 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. //
  2. // The graphics engine GXScene https://github.com/glscene
  3. //
  4. unit GXS.Nodes;
  5. (* Nodes are used to describe lines, polygons + more *)
  6. interface
  7. {.$I GLScene.Defines.inc}
  8. uses
  9. Winapi.OpenGL,
  10. System.Classes,
  11. System.SysUtils,
  12. System.Math,
  13. Stage.VectorTypes,
  14. Stage.Spline,
  15. Stage.VectorGeometry,
  16. GXS.Context,
  17. GXS.XOpenGL,
  18. GXS.BaseClasses,
  19. GXS.Coordinates;
  20. type
  21. TgxNode = class(TCollectionItem)
  22. private
  23. FCoords: TVector4f;
  24. FTagObject: TObject;
  25. procedure SetAsVector(const Value: TVector4f);
  26. procedure SetAsAffineVector(const Value: TAffineVector);
  27. function GetAsAffineVector: TAffineVector;
  28. procedure SetCoordinate(AIndex: Integer; AValue: Single);
  29. function GetCoordinate(const Index: Integer): Single;
  30. protected
  31. function StoreCoordinate(AIndex: Integer): Boolean;
  32. function GetDisplayName: string; override;
  33. public
  34. constructor Create(ACollection: TCollection); override;
  35. destructor Destroy; override;
  36. procedure Assign(Source: TPersistent); override;
  37. function AsAddress: PGLFloat;
  38. (* The coordinates viewed as a vector.
  39. Assigning a value to this property will trigger notification events,
  40. if you don't want so, use DirectVector instead. *)
  41. property AsVector: TVector4f read FCoords write SetAsVector;
  42. (* The coordinates viewed as an affine vector.
  43. Assigning a value to this property will trigger notification events,
  44. if you don't want so, use DirectVector instead.
  45. The W component is automatically adjustes depending on style. *)
  46. property AsAffineVector: TAffineVector read GetAsAffineVector write SetAsAffineVector;
  47. property W: Single index 3 read GetCoordinate write SetCoordinate stored StoreCoordinate;
  48. property TagObject: TObject read FTagObject write FTagObject;
  49. published
  50. property X: Single index 0 read GetCoordinate write SetCoordinate stored StoreCoordinate;
  51. property Y: Single index 1 read GetCoordinate write SetCoordinate stored StoreCoordinate;
  52. property Z: Single index 2 read GetCoordinate write SetCoordinate stored StoreCoordinate;
  53. end;
  54. TgxNodes = class(TOwnedCollection)
  55. protected
  56. procedure SetItems(Index: Integer; const Val: TgxNode);
  57. function GetItems(Index: Integer): TgxNode;
  58. procedure Update(Item: TCollectionItem); override;
  59. public
  60. constructor Create(AOwner: TPersistent; AItemClass: TCollectionItemClass = nil);
  61. function CreateCopy(AOwner: TPersistent): TgxNodes;
  62. function Add: TgxNode;
  63. function FindItemID(ID: Integer): TgxNode;
  64. property Items[index: Integer]: TgxNode read GetItems write SetItems; default;
  65. function First: TgxNode;
  66. function Last: TgxNode;
  67. procedure NotifyChange; virtual;
  68. procedure EndUpdate; override;
  69. procedure AddNode(const Coords: TgxCustomCoordinates); overload;
  70. procedure AddNode(const X, Y, Z: Single); overload;
  71. procedure AddNode(const Value: TVector4f); overload;
  72. procedure AddNode(const Value: TAffineVector); overload;
  73. procedure AddXYArc(XRadius, YRadius: Single; StartAngle, StopAngle: Single; NbSegments: Integer;
  74. const Center: TAffineVector);
  75. // Calculates and returns the barycenter of the nodes
  76. function Barycenter: TAffineVector;
  77. (* Computes normal based on the 1st three nodes.
  78. Returns NullVector if there are less than 3 nodes. *)
  79. function Normal: TAffineVector;
  80. // Returns normalized vector Nodes[i+1]-Nodes[i]
  81. function Vector(I: Integer): TAffineVector;
  82. (* Calculates the extents of the nodes (min-max for all coordinates).
  83. The returned values are also the two corners of the axis-aligned
  84. bounding box. *)
  85. procedure GetExtents(var Min, Max: TAffineVector);
  86. // Translate all nodes
  87. procedure Translate(const Tv: TAffineVector);
  88. // Scale all node coordinates
  89. procedure Scale(const Fv: TAffineVector); overload;
  90. // Scale all node coordinates
  91. procedure Scale(F: Single); overload;
  92. // Rotate nodes around Y axis by the given angle (degrees)
  93. procedure RotateAroundX(Angle: Single);
  94. // Rotate nodes around Y axis by the given angle (degrees)
  95. procedure RotateAroundY(Angle: Single);
  96. // Rotate nodes around Y axis by the given angle (degrees)
  97. procedure RotateAroundZ(Angle: Single);
  98. procedure RenderTesselatedPolygon(ATextured: Boolean; ANormal: PAffineVector = nil; ASplineDivisions: Integer = 1;
  99. AInvertNormals: Boolean = False);
  100. function CreateNewCubicSpline: TCubicSpline;
  101. end;
  102. TgxNodesClass = class of TgxNodes;
  103. //-----------------------------------------------------
  104. implementation
  105. //-----------------------------------------------------
  106. // ------------------
  107. // ------------------ TgxNode ------------------
  108. // ------------------
  109. constructor TgxNode.Create(ACollection: TCollection);
  110. begin
  111. inherited Create(ACollection);
  112. // nothing, yet
  113. end;
  114. destructor TgxNode.Destroy;
  115. begin
  116. // nothing, yet
  117. inherited Destroy;
  118. end;
  119. procedure TgxNode.Assign(Source: TPersistent);
  120. begin
  121. if Source is TgxNode then
  122. begin
  123. FCoords := TgxNode(Source).FCoords;
  124. end
  125. else
  126. inherited;
  127. end;
  128. function TgxNode.GetDisplayName: string;
  129. begin
  130. Result := Format('%.4f; %.4f; %.4f', [X, Y, Z]);
  131. end;
  132. function TgxNode.AsAddress: PGLFloat;
  133. begin
  134. Result := @FCoords;
  135. end;
  136. procedure TgxNode.SetAsVector(const Value: TVector4f);
  137. begin
  138. FCoords := Value;
  139. (Collection as TgxNodes).NotifyChange;
  140. end;
  141. procedure TgxNode.SetAsAffineVector(const Value: TAffineVector);
  142. begin
  143. SetVector(FCoords, Value);
  144. (Collection as TgxNodes).NotifyChange;
  145. end;
  146. function TgxNode.GetAsAffineVector: TAffineVector;
  147. begin
  148. SetVector(Result, FCoords);
  149. end;
  150. function TgxNode.GetCoordinate(const Index: Integer): Single;
  151. begin
  152. Result := FCoords.V[Index];
  153. end;
  154. procedure TgxNode.SetCoordinate(AIndex: Integer; AValue: Single);
  155. begin
  156. FCoords.V[AIndex] := AValue;
  157. (Collection as TgxNodes).NotifyChange;
  158. end;
  159. function TgxNode.StoreCoordinate(AIndex: Integer): Boolean;
  160. begin
  161. Result := (FCoords.V[AIndex] <> 0);
  162. end;
  163. // ------------------
  164. // ------------------ TgxNodes ------------------
  165. // ------------------
  166. constructor TgxNodes.Create(AOwner: TPersistent; AItemClass: TCollectionItemClass = nil);
  167. begin
  168. if not Assigned(AItemClass) then
  169. inherited Create(AOwner, TgxNode)
  170. else
  171. inherited Create(AOwner, AItemClass);
  172. end;
  173. function TgxNodes.CreateCopy(AOwner: TPersistent): TgxNodes;
  174. begin
  175. if Self <> nil then
  176. begin
  177. Result := TgxNodesClass(Self.ClassType).Create(AOwner);
  178. Result.Assign(Self);
  179. end
  180. else
  181. Result := nil;
  182. end;
  183. procedure TgxNodes.SetItems(Index: Integer; const Val: TgxNode);
  184. begin
  185. inherited Items[index] := Val;
  186. end;
  187. function TgxNodes.GetItems(Index: Integer): TgxNode;
  188. begin
  189. Result := TgxNode(inherited Items[index]);
  190. end;
  191. function TgxNodes.First: TgxNode;
  192. begin
  193. if Count > 0 then
  194. Result := TgxNode(inherited Items[0])
  195. else
  196. Result := nil;
  197. end;
  198. function TgxNodes.Last: TgxNode;
  199. var
  200. N: Integer;
  201. begin
  202. N := Count - 1;
  203. if N >= 0 then
  204. Result := TgxNode(inherited Items[N])
  205. else
  206. Result := nil;
  207. end;
  208. procedure TgxNodes.Update(Item: TCollectionItem);
  209. begin
  210. inherited;
  211. NotifyChange;
  212. end;
  213. function TgxNodes.Add: TgxNode;
  214. begin
  215. Result := (inherited Add) as TgxNode;
  216. end;
  217. function TgxNodes.FindItemID(ID: Integer): TgxNode;
  218. begin
  219. Result := (inherited FindItemID(ID)) as TgxNode;
  220. end;
  221. procedure TgxNodes.NotifyChange;
  222. begin
  223. if (UpdateCount = 0) and (GetOwner <> nil) and (GetOwner is TgxUpdateAbleComponent) then
  224. TgxUpdateAbleComponent(GetOwner).NotifyChange(Self);
  225. end;
  226. procedure TgxNodes.EndUpdate;
  227. begin
  228. inherited EndUpdate;
  229. // Workaround for a bug in VCL's EndUpdate
  230. if UpdateCount = 0 then
  231. NotifyChange;
  232. end;
  233. procedure TgxNodes.AddNode(const Coords: TgxCustomCoordinates);
  234. begin
  235. Add.AsVector := Coords.AsVector;
  236. end;
  237. procedure TgxNodes.AddNode(const X, Y, Z: Single);
  238. begin
  239. Add.AsVector := PointMake(X, Y, Z);
  240. end;
  241. procedure TgxNodes.AddNode(const Value: TVector4f);
  242. begin
  243. Add.AsVector := Value;
  244. end;
  245. procedure TgxNodes.AddNode(const Value: TAffineVector);
  246. begin
  247. Add.AsAffineVector := Value;
  248. end;
  249. procedure TgxNodes.AddXYArc(XRadius, YRadius: Single; StartAngle, StopAngle: Single; NbSegments: Integer;
  250. const Center: TAffineVector);
  251. var
  252. I: Integer;
  253. F: Single;
  254. S, C: Single;
  255. begin
  256. BeginUpdate;
  257. try
  258. StartAngle := DegToRadian(StartAngle);
  259. StopAngle := DegToRadian(StopAngle);
  260. F := (StopAngle - StartAngle) / NbSegments;
  261. for I := 0 to NbSegments do
  262. begin
  263. SinCosine(I * F + StartAngle, S, C);
  264. SetVector(Add.FCoords, Center.X + XRadius * C, Center.Y + YRadius * S, Center.Z, 1);
  265. end;
  266. finally
  267. EndUpdate;
  268. end;
  269. end;
  270. function TgxNodes.Barycenter: TAffineVector;
  271. var
  272. I: Integer;
  273. begin
  274. Result := NullVector;
  275. if Count > 0 then
  276. begin
  277. for I := 0 to Count - 1 do
  278. AddVector(Result, PAffineVector(Items[I].AsAddress)^);
  279. ScaleVector(Result, 1.0 / Count);
  280. end;
  281. end;
  282. function TgxNodes.Normal: TAffineVector;
  283. begin
  284. if Count >= 3 then
  285. CalcPlaneNormal(Items[0].FCoords, Items[1].FCoords, Items[2].FCoords, Result)
  286. else
  287. Result := NullVector;
  288. end;
  289. function TgxNodes.Vector(I: Integer): TAffineVector;
  290. procedure CalcUsingPrev; forward;
  291. procedure CalcUsingNext;
  292. begin
  293. if I < Count - 1 then
  294. VectorSubtract(Items[I].AsVector, Items[I + 1].AsVector, Result)
  295. else
  296. CalcUsingPrev;
  297. end;
  298. procedure CalcUsingPrev;
  299. begin
  300. if I > 0 then
  301. VectorSubtract(Items[I - 1].AsVector, Items[I].AsVector, Result)
  302. else
  303. CalcUsingNext;
  304. end;
  305. var
  306. J: Integer;
  307. Vecnull: Boolean;
  308. begin
  309. Assert((I >= 0) and (I < Count));
  310. if I = 0 then
  311. if I = Count - 1 then
  312. SetVector(Result, NullVector)
  313. else
  314. VectorSubtract(Items[I + 1].AsVector, Items[I].AsVector, Result)
  315. else if I = Count - 1 then
  316. VectorSubtract(Items[I].AsVector, Items[I - 1].AsVector, Result)
  317. else
  318. VectorSubtract(Items[I + 1].AsVector, Items[I - 1].AsVector, Result);
  319. if VectorNorm(Result) < 1E-5 then
  320. begin
  321. // avoid returning null vector which generates display bugs in geometry
  322. J := 1;
  323. Vecnull := True;
  324. while (I + J < Count) and (Vecnull) do
  325. begin
  326. VectorSubtract(Items[I + J].AsVector, Items[I].AsVector, Result);
  327. if (VectorNorm(Result) > 1E-5) then
  328. Vecnull := False
  329. else
  330. Inc(J);
  331. end;
  332. J := 1;
  333. while (I - J > 0) and (Vecnull) do
  334. begin
  335. VectorSubtract(Items[I].AsVector, Items[I - J].AsVector, Result);
  336. if (VectorNorm(Result) > 1E-5) then
  337. Vecnull := False
  338. else
  339. Inc(J);
  340. end;
  341. if Vecnull then
  342. SetVector(Result, NullVector)
  343. else
  344. NormalizeVector(Result);
  345. end
  346. else
  347. NormalizeVector(Result);
  348. end;
  349. procedure TgxNodes.GetExtents(var Min, Max: TAffineVector);
  350. var
  351. I, K: Integer;
  352. F: Single;
  353. const
  354. CBigValue: Single = 1E50;
  355. CSmallValue: Single = -1E50;
  356. begin
  357. SetVector(Min, CBigValue, CBigValue, CBigValue);
  358. SetVector(Max, CSmallValue, CSmallValue, CSmallValue);
  359. for I := 0 to Count - 1 do
  360. begin
  361. for K := 0 to 2 do
  362. begin
  363. F := PAffineVector(Items[I].AsAddress)^.V[K];
  364. if F < Min.V[K] then
  365. Min.V[K] := F;
  366. if F > Max.V[K] then
  367. Max.V[K] := F;
  368. end;
  369. end;
  370. end;
  371. procedure TgxNodes.Translate(const Tv: TAffineVector);
  372. var
  373. I: Integer;
  374. begin
  375. for I := 0 to Count - 1 do
  376. AddVector(PAffineVector(Items[I].AsAddress)^, Tv);
  377. NotifyChange;
  378. end;
  379. procedure TgxNodes.Scale(const Fv: TAffineVector);
  380. var
  381. I: Integer;
  382. begin
  383. for I := 0 to Count - 1 do
  384. ScaleVector(PAffineVector(Items[I].AsAddress)^, Fv);
  385. NotifyChange;
  386. end;
  387. procedure TgxNodes.Scale(F: Single);
  388. var
  389. I: Integer;
  390. begin
  391. for I := 0 to Count - 1 do
  392. ScaleVector(PAffineVector(Items[I].AsAddress)^, F);
  393. NotifyChange;
  394. end;
  395. procedure TgxNodes.RotateAroundX(Angle: Single);
  396. var
  397. I: Integer;
  398. C, S, V2: Single;
  399. V: PAffineVector;
  400. begin
  401. SinCosine(CPIDiv180 * Angle, S, C);
  402. for I := 0 to Count - 1 do
  403. begin
  404. V := PAffineVector(Items[I].AsAddress);
  405. V2 := V^.Z;
  406. V^.Y := C * V^.Y + S * V2;
  407. V^.Z := C * V2 - S * V^.Y;
  408. end;
  409. NotifyChange;
  410. end;
  411. procedure TgxNodes.RotateAroundY(Angle: Single);
  412. var
  413. I: Integer;
  414. C, S, V0: Single;
  415. V: PAffineVector;
  416. begin
  417. SinCosine(CPIDiv180 * Angle, S, C);
  418. for I := 0 to Count - 1 do
  419. begin
  420. V := PAffineVector(Items[I].AsAddress);
  421. V0 := V^.X;
  422. V^.X := C * V0 + S * V^.Z;
  423. V^.Z := C * V^.Z - S * V0;
  424. end;
  425. NotifyChange;
  426. end;
  427. procedure TgxNodes.RotateAroundZ(Angle: Single);
  428. var
  429. I: Integer;
  430. C, S, V1: Single;
  431. V: PAffineVector;
  432. begin
  433. SinCosine(CPIDiv180 * Angle, S, C);
  434. for I := 0 to Count - 1 do
  435. begin
  436. V := PAffineVector(Items[I].AsAddress);
  437. V1 := V^.Y;
  438. V^.Y := C * V1 + S * V^.X;
  439. V^.X := C * V^.X - S * V1;
  440. end;
  441. NotifyChange;
  442. end;
  443. function TgxNodes.CreateNewCubicSpline: TCubicSpline;
  444. var
  445. I: Integer;
  446. Xa, Ya, Za: PFloatArray;
  447. begin
  448. GetMem(Xa, SizeOf(Single) * Count);
  449. GetMem(Ya, SizeOf(Single) * Count);
  450. GetMem(Za, SizeOf(Single) * Count);
  451. for I := 0 to Count - 1 do
  452. with Items[I] do
  453. begin
  454. Xa^[I] := X;
  455. Ya^[I] := Y;
  456. Za^[I] := Z;
  457. end;
  458. Result := TCubicSpline.Create(Xa, Ya, Za, nil, Count);
  459. FreeMem(Xa);
  460. FreeMem(Ya);
  461. FreeMem(Za);
  462. end;
  463. var
  464. NbExtraVertices: Integer;
  465. NewVertices: PAffineVectorArray;
  466. function AllocNewVertex: PAffineVector;
  467. begin
  468. Inc(NbExtraVertices);
  469. Result := @NewVertices[NbExtraVertices - 1];
  470. end;
  471. procedure TessError(Errno: GLEnum);
  472. {$IFDEF Win32} stdcall; {$ENDIF}{$IFDEF UNIX} cdecl; {$ENDIF}
  473. begin
  474. Assert(False, IntToStr(Errno) + ': ' + string(GluErrorString(Errno)));
  475. end;
  476. procedure TessIssueVertex(VertexData: Pointer);
  477. {$IFDEF Win32} stdcall; {$ENDIF}{$IFDEF UNIX} cdecl; {$ENDIF}
  478. begin
  479. glTexCoord2fv(VertexData);
  480. glVertex3fv(VertexData);
  481. end;
  482. procedure TessCombine(Coords: PDoubleVector; Vertex_data: Pointer; Weight: PGLFloat; var OutData: Pointer);
  483. {$IFDEF Win32} stdcall; {$ENDIF}{$IFDEF UNIX} cdecl; {$ENDIF}
  484. begin
  485. OutData := AllocNewVertex;
  486. SetVector(PAffineVector(OutData)^, Coords^[0], Coords^[1], Coords^[2]);
  487. end;
  488. procedure TgxNodes.RenderTesselatedPolygon(ATextured: Boolean; ANormal: PAffineVector = nil; ASplineDivisions: Integer = 1;
  489. AInvertNormals: Boolean = False);
  490. var
  491. I: Integer;
  492. Tess: GLUTesselator;
  493. DblVector: TAffineDblVector;
  494. Spline: TCubicSpline;
  495. SplinePos: PAffineVector;
  496. F: Single;
  497. begin
  498. if Count > 2 then
  499. begin
  500. // Create and initialize the GLU tesselator
  501. Tess := gluNewTess;
  502. gluTessCallback(Tess, GLU_TESS_BEGIN, @glBegin);
  503. if ATextured then
  504. gluTessCallback(Tess, GLU_TESS_VERTEX, @TessIssueVertex)
  505. else
  506. gluTessCallback(Tess, GLU_TESS_VERTEX, @glVertex3fv);
  507. gluTessCallback(Tess, GLU_TESS_END, @glEnd);
  508. gluTessCallback(Tess, GLU_TESS_ERROR, @TessError);
  509. gluTessCallback(Tess, GLU_TESS_COMBINE, @TessCombine);
  510. NbExtraVertices := 0;
  511. // Issue normal
  512. if Assigned(ANormal) then
  513. begin
  514. glNormal3fv(PGLFloat(ANormal));
  515. gluTessNormal(Tess, ANormal^.X, ANormal^.Y, ANormal^.Z);
  516. end;
  517. // Issue polygon
  518. gluTessBeginPolygon(Tess, nil);
  519. gluTessBeginContour(Tess);
  520. if ASplineDivisions <= 1 then
  521. begin
  522. // no spline, use direct coordinates
  523. GetMem(NewVertices, Count * SizeOf(TAffineVector));
  524. if AInvertNormals then
  525. begin
  526. for I := Count - 1 downto 0 do
  527. begin
  528. SetVector(DblVector, PAffineVector(Items[I].AsAddress)^);
  529. gluTessVertex(Tess, @DblVector, Items[I].AsAddress);
  530. end;
  531. end
  532. else
  533. begin
  534. for I := 0 to Count - 1 do
  535. begin
  536. SetVector(DblVector, PAffineVector(Items[I].AsAddress)^);
  537. gluTessVertex(Tess, @DblVector, Items[I].AsAddress);
  538. end;
  539. end;
  540. end
  541. else
  542. begin
  543. // cubic spline
  544. GetMem(NewVertices, 2 * ASplineDivisions * Count * SizeOf(TAffineVector));
  545. Spline := CreateNewCubicSpline;
  546. F := 1.0 / ASplineDivisions;
  547. if AInvertNormals then
  548. begin
  549. for I := ASplineDivisions * (Count - 1) downto 0 do
  550. begin
  551. SplinePos := AllocNewVertex;
  552. Spline.SplineAffineVector(I * F, SplinePos^);
  553. SetVector(DblVector, SplinePos^);
  554. gluTessVertex(Tess, @DblVector, SplinePos);
  555. end;
  556. end
  557. else
  558. begin
  559. for I := 0 to ASplineDivisions * (Count - 1) do
  560. begin
  561. SplinePos := AllocNewVertex;
  562. Spline.SplineAffineVector(I * F, SplinePos^);
  563. SetVector(DblVector, SplinePos^);
  564. gluTessVertex(Tess, @DblVector, SplinePos);
  565. end;
  566. end;
  567. Spline.Free;
  568. end;
  569. gluTessEndContour(Tess);
  570. gluTessEndPolygon(Tess);
  571. // release stuff
  572. if Assigned(NewVertices) then
  573. FreeMem(NewVertices);
  574. gluDeleteTess(Tess);
  575. end;
  576. end;
  577. end.