2
// This unit is part of the GLScene Engine https://github.com/glscene
5
Line implementation by means of a Triangle strip.
6
The history is logged in a former GLS version of the unit.
37
{Specialized Node for use in a TGLLines objects.
38
Adds a Width property }
39
TLineNode = class(TGLNode)
44
constructor Create(Collection : TCollection); override;
45
destructor Destroy; override;
46
procedure Assign(Source: TPersistent); override;
47
property Data: Pointer read FData write FData;
53
{Specialized collection for Nodes in TGLMeshLines objects.
54
Stores TLineNode items. }
55
TLineNodes = class(TGLNodes)
58
constructor Create(AOwner : TComponent); overload;
59
destructor destroy; override;
60
procedure NotifyChange; override;
61
function IndexOf(LineNode: TLineNode): Integer;
64
TLineItem = class(TCollectionItem)
70
FSplineMode: TGLLineSplineMode;
71
FTextureLength: Single;
73
FTextureCorrection: Boolean;
76
procedure SetHide(const Value: Boolean);
77
procedure SetTextureCorrection(const Value: Boolean);
78
procedure SetBreakAngle(const Value: Single);
79
procedure SetDivision(const Value: Integer);
80
procedure SetNodes(const Value: TLineNodes);
81
procedure SetSplineMode(const Value:TGLLineSplineMode);
82
procedure SetTextureLength(const Value: Single);
83
procedure SetWidth(const Value: Single);
85
procedure DoChanged; virtual;
87
property Data: Pointer read FData write FData;
88
constructor Create(Collection: TCollection); override;
89
destructor Destroy; override;
92
property Hide: Boolean read FHide write SetHide;
93
property Name: String read FName write FName;
94
property TextureCorrection: Boolean read FTextureCorrection write SetTextureCorrection;
95
property BreakAngle: Single read FBreakAngle write SetBreakAngle;
96
property Division: Integer read FDivision write SetDivision;
97
property Nodes : TLineNodes read FNodes write SetNodes;
98
property SplineMode: TGLLineSplineMode read FSplineMode write SetSplineMode;
99
property TextureLength: Single read FTextureLength write SetTextureLength;
100
property Width: Single read FWidth write SetWidth;
103
TLineCollection = class(TOwnedCollection)
105
procedure SetItems(Index: Integer; const Val: TLineItem);
106
function GetItems(Index: Integer): TLineItem;
109
function Add: TLineItem; overload;
110
function Add(Name: String): TLineItem; overload;
111
property Items[Index: Integer]: TLineItem read GetItems write SetItems; default;
115
TLightmapBounds = class(TGLCustomCoordinates)
117
function GetLeft: TGLFloat;
118
function GetTop: TGLFloat;
119
function GetRight: TGLFloat;
120
function GetBottom: TGLFloat;
121
function GetWidth: TGLFloat;
122
function GetHeight: TGLFloat;
123
procedure SetLeft(const value: TGLFloat);
124
procedure SetTop(const value: TGLFloat);
125
procedure SetRight(const value: TGLFloat);
126
procedure SetBottom(const value: TGLFloat);
128
property Left: TGLFloat read GetLeft write SetLeft stored False;
129
property Top: TGLFloat read GetTop write SetTop stored False;
130
property Right: TGLFloat read GetRight write SetRight stored False;
131
property Bottom: TGLFloat read GetBottom write SetBottom stored False;
132
property Width: TGLFloat read GetWidth;
133
property Height: TGLFloat read GetHeight;
136
TGLMeshLines = class(TGLFreeForm)
138
FLines: TLineCollection;
139
FMesh: TGLMeshObject;
140
FLightmapBounds: TLightmapBounds;
141
FLightmapIndex: Integer;
142
FLightmapMaterialName: String;
143
FFaceGroup: TFGVertexIndexList;
148
FSelectedLineItem: TLineItem;
149
FSelectedNode: TLineNode;
150
FNode1,FNode2: TLineNode;
151
function GetUpdating: Boolean;
152
function PointNearLine(const LineItem: TLineItem; const X,Z: Single; Tolerance: single = 1): boolean;
153
function PointNearSegment(const StartNode, EndNode: TLineNode; const X,Z: Single; LineWidth: single; Tolerance: single = 1): boolean;
154
procedure StitchStrips(idx: TIntegerList);
155
procedure AddStitchMarker(idx: TIntegerList);
156
procedure SetShowNodes(const Value: Boolean);
157
procedure SetNoZWrite(const Value: Boolean);
158
procedure SetLightmapIndex(const value: Integer);
159
procedure SetLightmapMaterialName(const value: String);
160
procedure SetLightmapBounds(const value: TLightmapBounds);
163
procedure AddVertices(Up, Inner, Outer: TAffineVector; S: Single; Correction: Single; UseDegenerate: Boolean; LineItem: TLineItem);
164
procedure BuildLineItem(LineItem: TLineItem);
165
procedure BuildGeometry;
166
procedure DrawNode(var rci : TGLRenderContextInfo; Node: TLineNode; LineWidth: Single);
167
procedure DrawCircle(Radius: Single);
168
function SelectNode(LineItem: TLineItem; X,Z: Single):TLineNode;
170
procedure Loaded; override;
172
constructor Create(AOwner: TComponent); override;
173
destructor Destroy; override;
174
procedure BeginUpdate; override;
175
procedure EndUpdate; override;
177
function SelectLineItem(const X,Z: Single; Tolerance: single = 1): TLineItem; overload;
178
function SelectLineItem(LineItem: TLineItem): TLineItem; overload;
179
function SelectLineItem(LineNode: TLineNode): TLineItem; overload;
180
procedure DeselectLineItem;
181
procedure DeselectLineNode;
182
procedure BuildList(var rci : TGLRenderContextInfo); override;
183
procedure DoRender(var rci : TGLRenderContextInfo; renderSelf, renderChildren : Boolean); override;
184
procedure NotifyChange(Sender : TObject); override;
185
property SelectedLineItem: TLineItem read FSelectedLineItem;
186
property SelectedNode: TLineNode read FSelectedNode;
187
property Node1: TLineNode read FNode1;
188
property Node2: TLineNode read FNode2;
191
property Updating: Boolean Read GetUpdating;
192
property Lines: TLineCollection read FLines;
194
property LightmapBounds: TLightmapBounds read FLightmapBounds write SetLightmapBounds;
195
property LightmapIndex: Integer read FLightmapIndex write SetLightmapIndex;
196
property LightmapMaterialName: String read FLightmapMaterialName write SetLightmapMaterialName;
197
property NoZWrite: boolean read FNoZWrite write SetNoZWrite;
198
property ShowNodes: Boolean read FSHowNodes write SetShowNodes;
201
//--------------------------------------------------------------------------
202
//--------------------------------------------------------------------------
203
//--------------------------------------------------------------------------
205
//--------------------------------------------------------------------------
206
//--------------------------------------------------------------------------
207
//--------------------------------------------------------------------------
214
constructor TLineNode.Create(Collection : TCollection);
216
inherited Create(Collection);
219
destructor TLineNode.Destroy;
224
procedure TLineNode.Assign(Source: TPersistent);
226
if Source is TLineNode then
228
FData := TLineNode(Source).FData;
235
constructor TLineNodes.Create(AOwner : TComponent);
237
inherited Create(AOwner, TLineNode);
240
destructor TLineNodes.destroy;
245
procedure TLineNodes.NotifyChange;
247
if (GetOwner<>nil) then
248
TGLMeshLines((GetOwner as TLineItem).Collection.Owner).StructureChanged;
251
function TLineNodes.IndexOf(LineNode: TLineNode): Integer;
256
if assigned(LineNode) then
258
for i := 0 to Count - 1 do
260
if LineNode = Items[i] then
272
function TLineCollection.GetItems(index: Integer): TLineItem;
274
Result:=TLineItem(inherited Items[index]);
277
procedure TLineCollection.SetItems(index: Integer; const val: TLineItem);
279
inherited Items[index]:=val;
282
function TLineCollection.Add: TLineItem;
284
result := TLineItem.Create(self);
287
function TLineCollection.Add(Name: String): TLineItem;
295
constructor TLineItem.Create(Collection: TCollection);
298
FNodes:=TLineNodes.Create(Self, TLineNode);
301
FSplineMode := lsmLines;
304
FTextureCorrection := False;
307
destructor TLineItem.Destroy;
309
if TGLMeshLines(Collection.Owner).SelectedLineItem = self then
310
TGLMeshLines(Collection.Owner).DeSelectLineItem;
315
procedure TLineItem.SetHide(const Value: Boolean);
321
procedure TLineItem.SetTextureCorrection(const Value: Boolean);
323
FTextureCorrection := Value;
327
procedure TLineItem.SetBreakAngle(const Value: Single);
329
FBreakAngle := Value;
333
procedure TLineItem.SetDivision(const Value: Integer);
339
procedure TLineItem.SetNodes(const Value: TLineNodes);
341
FNodes.Assign(Value);
345
procedure TLineItem.SetSplineMode(const Value:TGLLineSplineMode);
347
FSplineMode := Value;
351
procedure TLineItem.SetTextureLength(const Value: Single);
353
FTextureLength := Value;
357
procedure TLineItem.SetWidth(const Value: Single);
363
procedure TLineItem.DoChanged;
365
//Notify parent of change because the mesh needs to be regenerated
366
if (GetOwner<>nil) then
367
TGLMeshLines(Collection.Owner).NotifyChange(Self);
372
function TLightmapBounds.GetLeft: TGLFloat;
377
function TLightmapBounds.GetTop: TGLFloat;
382
function TLightmapBounds.GetRight: TGLFloat;
387
function TLightmapBounds.GetBottom: TGLFloat;
392
function TLightmapBounds.GetWidth: TGLFloat;
397
function TLightmapBounds.GetHeight: TGLFloat;
402
procedure TLightmapBounds.SetLeft(const value: TGLFloat);
407
procedure TLightmapBounds.SetTop(const value: TGLFloat);
412
procedure TLightmapBounds.SetRight(const value: TGLFloat);
417
procedure TLightmapBounds.SetBottom(const value: TGLFloat);
425
constructor TGLMeshLines.Create(AOwner: TComponent);
428
FLines := TLineCollection.Create(self,TLineItem);
429
FLightmapBounds := TLightmapBounds.Create(Self);
432
destructor TGLMeshLines.Destroy;
435
FLightmapBounds.Free;
439
procedure TGLMeshLines.Loaded;
444
procedure TGLMeshLines.BeginUpdate;
449
procedure TGLMeshLines.EndUpdate;
452
if FUpdating < 1 then
459
procedure TGLMeshLines.Clear;
461
FSelectedLineItem := nil;
462
FSelectedNode := nil;
468
procedure TGLMeshLines.BuildList(var rci : TGLRenderContextInfo);
475
for i:= 0 to Lines.Count - 1 do
477
if Lines[i] = FSelectedLineItem then
479
for j := 0 to Lines[i].Nodes.Count-1 do
480
DrawNode(rci, TLineNode(Lines[i].Nodes[j]),Lines[i].Width);
486
procedure TGLMeshLines.DoRender(var rci : TGLRenderContextInfo; renderSelf, renderChildren : Boolean);
490
GL.Disable(GL_Depth_Test);
492
GL.Enable(GL_Depth_Test);
498
procedure TGLMeshLines.SetShowNodes(const Value: Boolean);
504
procedure TGLMeshLines.SetNoZWrite(const Value: Boolean);
510
procedure TGLMeshLines.SetLightmapIndex(const value: Integer);
512
FLightmapIndex := Value;
516
procedure TGLMeshLines.SetLightmapMaterialName(const value: String);
518
lLibMaterial: TGLLibMaterial;
522
if assigned(LightmapLibrary) then
524
lLibMaterial := LightmapLibrary.Materials.GetLibMaterialByName(Value);
525
if assigned(lLibMaterial) then
527
FLightmapIndex := lLibMaterial.ID;
528
FLightmapMaterialName := Value;
535
procedure TGLMeshLines.SetLightmapBounds( const value: TLightmapBounds );
537
FLightmapBounds.SetVector(value.X, value.Y,value.Z,value.W);
541
procedure TGLMeshLines.DoChanged;
543
if Updating then exit;
548
procedure TGLMeshLines.BuildGeometry;
551
lFirstLineDone: Boolean;
552
lVertex: TAffineVector;
553
lTextPoint: TTexPoint;
555
if Updating then exit;
559
lFirstLineDone := False;
560
FMesh := TGLMeshObject.CreateOwned(FMeshObjects);
561
FMesh.Mode := momFaceGroups;
562
FFaceGroup := TFGVertexIndexList.CreateOwned(FMesh.FaceGroups);
563
FFaceGroup.Mode := fgmmTriangleStrip;
564
FFaceGroup.LightMapIndex := FLightmapIndex;
566
for i := 0 to Lines.Count - 1 do
568
if not FLines.Items[i].Hide then
570
if lFirstLineDone then
571
AddStitchMarker(FFaceGroup.VertexIndices);
572
if TLineItem(FLines.Items[i]).Nodes.Count > 0 then
574
BuildLineItem(TLineItem(FLines.Items[i]));
575
lFirstLineDone := True;
579
StitchStrips(FFaceGroup.VertexIndices);
580
//Calculate lightmapping
581
if assigned(LightmapLibrary) and (LightmapIndex <> -1 ) then
582
for i := 0 to FMesh.Vertices.Count - 1 do
584
lVertex := FMesh.Vertices.Items[i];
585
lTextPoint.s := (lVertex.X - FLightmapBounds.Left) / FLightmapBounds.Width;
586
lTextPoint.t := (lVertex.Z - FLightmapBounds.Top) / FLightmapBounds.Height;
587
FMesh.LightMapTexCoords.Add(lTextPoint);
591
procedure TGLMeshLines.DrawNode(var rci : TGLRenderContextInfo; Node: TLineNode; LineWidth: Single);
595
lNodeSize := LineWidth* 0.7;
597
GL.Translatef(Node.x,Node.y,Node.z);
598
if lNodeSize <>1 then
601
GL.Scalef(lNodeSize, lNodeSize, lNodeSize);
602
/// rci.GLStates.UnSetGLState(stTexture2D);
603
// rci.GLStates.UnSetGLState(stColorMaterial);
604
// rci.GLStates.UnSetGLState(stBlend);
605
if Node = FSelectedNode then
606
rci.GLStates.SetGLMaterialColors(cmFRONT, clrBlack, clrGray20, clrYellow, clrBlack, 0)
608
rci.GLStates.SetGLMaterialColors(cmFRONT, clrBlack, clrGray20, clrGreen, clrBlack, 0);
609
DrawCircle(lNodeSize);
614
if Node = FSelectedNode then
615
rci.GLStates.SetGLMaterialColors(cmFRONT, clrBlack, clrGray20, clrYellow, clrBlack, 0)
617
rci.GLStates.SetGLMaterialColors(cmFRONT, clrBlack, clrGray20, clrGreen, clrBlack, 0);
618
DrawCircle(lNodeSize);
623
procedure TGLMeshLines.DrawCircle(Radius: Single);
625
inner,outer,p1,p2: TVector;
630
inner := VectorMake(1, 0, 0);
631
outer := VectorMake(1.3, 0, 0);
632
GL.Begin_(GL_TRIANGLE_STRIP);
633
for i:= 0 to CIRCLESEGMENTS do
635
a := i * 2 * pi / CIRCLESEGMENTS;
638
lUp := Up.AsAffineVector;
639
RotateVector(p1,lUp, a);
640
RotateVector(p2,lUp, a);
647
function TGLMeshLines.SelectNode(LineItem: TLineItem; X,Z: Single): TLineNode;
654
lRange := LineItem.Width * 0.88;
655
for i := 0 to LineItem.Nodes.count - 1 do
657
length := 1/RLength((X - LineItem.Nodes[i].X),(Z - LineItem.Nodes[i].Z));
658
if length < lRange then
660
Result := TLineNode(LineItem.Nodes[i]);
666
function TGLMeshLines.SelectLineItem(LineItem: TLineItem): TLineItem;
669
FSelectedLineItem := LineItem;
670
FSelectedNode := nil;
674
function TGLMeshLines.SelectLineItem(LineNode: TLineNode): TLineItem;
676
FSelectedLineItem := TLineItem(LineNode.Collection.Owner);
677
FSelectedNode := LineNode;
678
Result := FSelectedLineItem;
682
procedure TGLMeshLines.DeselectLineItem;
684
FSelectedLineItem := nil;
685
FSelectedNode := nil;
689
procedure TGLMeshLines.DeselectLineNode;
691
FSelectedNode := nil;
695
function TGLMeshLines.SelectLineItem(const X,Z: Single; Tolerance: single = 1): TLineItem;
698
lStartPoint: Integer;
703
if Assigned(FSelectedLineItem) then
704
lStartPoint := FSelectedLineItem.ID + 1
707
for i := lStartPoint to FLines.Count - 1 do
709
if (FLines[i] <> FSelectedLineItem) then
711
if PointNearLine(FLines[i],X,Z,Tolerance) then
714
lNode := SelectNode(FLines[i], X,Z);
715
if lNode <> FSelectedNode then
717
FSelectedNode := lNode;
724
if not Assigned(Result) then
726
for i := 0 to lStartPoint - 1 do
728
if FLines[i] <> FSelectedLineItem then
730
if PointNearLine(FLines[i],X,Z,Tolerance) then
739
FSelectedLineItem := Result;
740
if not assigned(FSelectedLineItem) then
742
FSelectedNode := nil;
749
function TGLMeshLines.GetUpdating: Boolean;
751
Result := FUpdating > 0;
754
function TGLMeshLines.PointNearLine(const LineItem: TLineItem; const X,Z: Single; Tolerance: single = 1): boolean;
757
lStartNode,lEndNode: TLineNode;
760
for i := 0 to LineItem.Nodes.Count - 2 do
762
lStartNode := TLineNode(LineItem.Nodes[i]);
763
lEndNode := TLineNode(LineItem.Nodes[i+1]);
764
if PointNearSegment(lStartNode,lEndNode,X,Z,LineItem.Width,Tolerance) then
767
FNode1 := lStartNode;
774
function TGLMeshLines.PointNearSegment(const StartNode, EndNode: TLineNode; const X,Z: Single; LineWidth: single; Tolerance: single = 1): boolean;
776
xt, yt, u, len: single;
781
lDist := (LineWidth/2) * Tolerance;
782
xt:= EndNode.X - StartNode.X;
783
yt:= EndNode.Z - StartNode.Z;
784
len:= sqrt(xt*xt + yt*yt);
785
xp:= (X - StartNode.X);
786
yp:= (Z - StartNode.Z);
787
u:= (xp * xt + yp * yt) / len;
789
if (u < -lDist) or (u > len+lDist) then
792
// get the point on the line that's pependicular to the point in question
793
xt:= StartNode.X + xt * u;
794
yt:= StartNode.Z + yt * u;
795
// find the distance to the line, and see if it's closer than the specified distance
796
Result:= sqrt(sqr(xt - X) + sqr(yt - Z)) <= lDist;
799
procedure TGLMeshLines.StitchStrips(idx: TIntegerList);
804
for i := idx.Count - 1 downto 0 do
819
procedure TGLMeshLines.AddStitchMarker(idx: TIntegerList);
827
procedure TGLMeshLines.NotifyChange(Sender : TObject);
833
procedure TGLMeshLines.AddIndex;
835
FFaceGroup.Add(FIndex);
839
procedure TGLMeshLines.AddVertices(Up,Inner,Outer: TAffineVector; S: Single; Correction: Single; UseDegenerate: Boolean; LineItem: TLineItem);
841
if not LineItem.TextureCorrection then
844
Correction := Correction / (LineItem.TextureLength / LineItem.width);
845
FMesh.Normals.Add(Up);
846
FMesh.Vertices.Add(Outer);
847
FMesh.TexCoords.Add(S-Correction,1);
849
FMesh.Normals.Add(Up);
850
FMesh.TexCoords.Add(S+Correction,0);
851
FMesh.Vertices.Add(Inner);
853
if LineItem.TextureCorrection then
855
FMesh.Normals.Add(Up);
856
FMesh.Vertices.Add(Outer);
857
FMesh.TexCoords.Add(S+Correction,1);
859
FMesh.Normals.Add(Up);
860
FMesh.TexCoords.Add(S-Correction,0);
861
FMesh.Vertices.Add(Inner);
866
procedure TGLMeshLines.BuildLineItem(LineItem: TLineItem);
870
NSeg1: TAffineVector;
871
NSeg2: TAffineVector;
872
N1,N2,N3: TAffineVector;
873
Inner: TAffineVector;
874
Outer: TAffineVector;
877
lAngleOffset: Single;
878
lTotalAngleChange: Single;
883
lSpline: TCubicSpline;
887
lHalfLineWidth: single;
890
lTotalAngleChange := 0;
891
lHalfLineWidth := LineItem.Width/2;
892
lBreakAngle := DegToRad(LineItem.BreakAngle);
894
N1 := AffineVectorMake(0,0,0);
895
N2 := AffineVectorMake(0,0,0);
896
N3 := AffineVectorMake(0,0,0);
900
lUp := Up.AsAffineVector;
902
if LineItem.SplineMode = lsmLines then
903
lCount := LineItem.Nodes.Count - 1
905
if LineItem.Nodes.Count > 1 then
907
lCount := (LineItem.Nodes.Count-1) * LineItem.Division;
908
lSpline := LineItem.Nodes.CreateNewCubicSpline;
909
f:=1/LineItem.Division;
911
for i := 0 to lCount do
913
if LineItem.SplineMode = lsmLines then
915
N3 := LineItem.Nodes.Items[i].AsAffineVector
921
lSpline.SplineXYZ(i*f, a, b, c);
922
N3 := AffineVectorMake(a,b,c);
927
Seg2 := VectorSubtract(N3,N2);
930
if (i = 1) and not VectorEQuals(Seg2,NullVector)then
932
//Create start vertices
933
//this makes the assumption that these vectors are different which not always true
934
Inner := VectorCrossProduct(Seg2, lUp);
935
NormalizeVector(Inner);
936
ScaleVector(Inner,lHalfLineWidth);
937
Outer := VectorNegate(Inner);
940
AddVertices(lUp,Inner, Outer,S,0,False,LineItem);
941
s := s + VectorLength(Seg2)/LineItem.TextureLength;
945
lUp := VectorCrossProduct(Seg2,Seg1);
946
if VectorEquals(lUp, NullVector) then
947
lUp := Up.AsAffineVector;
948
Flip := VectorAngleCosine(lUp,Self.up.AsAffineVector) < 0;
951
NSeg1 := VectorNormalize(Seg1);
952
NSeg2 := VectorNormalize(Seg2);
953
if VectorEquals(NSeg1,NSeg2) then
955
Inner := VectorCrossProduct(Seg2, lUp);
960
Inner := VectorSubtract(NSeg2,NSeg1);
961
lAngle := (1.5707963 - ArcCos(VectorLength(Inner)/2));
963
lTotalAngleChange := lTotalAngleChange + (lAngle * 2);
964
//Create intermediate vertices
965
if (lTotalAngleChange > lBreakAngle) or (LineItem.BreakAngle = -1 )then
967
lTotalAngleChange := 0;
968
//Correct width for angles less than 170
969
if lAngle < 1.52 then
970
lAngleOffset := lHalfLineWidth * sqrt(sqr(Tan(lAngle))+1)
972
lAngleOffset := lHalfLineWidth;
973
NormalizeVector(Inner);
974
ScaleVector(Inner,lAngleOffset);
975
Outer := VectorNegate(Inner);
979
AddVertices(lUp,Inner, Outer,S,-Tan(lAngle)/2,True, LineItem)
981
AddVertices(lUp,Outer, Inner,S,Tan(lAngle)/2,True, LineItem);
983
s:= s + VectorLength(seg2)/LineItem.TextureLength;
986
//Create last vertices
987
if (lCount > 0) and (i = lCount) and not VectorEQuals(Seg2,NullVector) then
989
lUp := Up.AsAffineVector;
990
Inner := VectorCrossProduct(Seg2, lUp);
991
NormalizeVector(Inner);
992
ScaleVector(Inner,lHalfLineWidth);
993
Outer := VectorNegate(Inner);
996
AddVertices(lUp,Inner, Outer,S,0,False, LineItem);
1003
raise exception.Create(e.Message);
1005
if assigned(lSpline) then
1011
RegisterClasses([TGLMeshLines]);