const cZero2D: TPoing2D_ = (X: 0; Y: 0); // Default guaranteed miminum angle for quality mesh generator cDefaultMinimumAngle: Double = 30; // limited on 41.4 degrees and lower; // Default minimum segment length cDefaultMinimumSegmentLength: Double = 0.5; cOneThird: Double = 1 / 3; // Default precision when triangulating cDefaultTriangulationPrecision = 1E-3; // raise exception message sAddingNonUniqueObject = 'Adding non-unique object to list is not allowed'; sListMustBeSorted = 'List must be sorted'; sInvalidTriangleForSegment = 'Invalid triangle for segment'; sTriangleVertexHookupError = 'triangle-vertex hookup error'; sNoTriangleForVertex = 'No triangle for vertex'; sCrossSegmentIntersectionError = 'CrossSegment-Intersection Error'; function CompareCardinal(C1, C2: Cardinal): integer; begin if C1 < C2 then Result := -1 else if C1 > C2 then Result := 1 else Result := 0; end; function CompareInteger(Int1, Int2: integer): integer; begin if Int1 < Int2 then Result := -1 else if Int1 > Int2 then Result := 1 else Result := 0; end; function CompareInt64(const Int1, Int2: int64): integer; begin if Int1 < Int2 then Result := -1 else if Int1 > Int2 then Result := 1 else Result := 0; end; function ComparePointer(Item1, Item2: pointer): integer; begin if integer(Item1) < integer(Item2) then Result := -1 else if integer(Item1) > integer(Item2) then Result := 1 else Result := 0; end; function CompareBool(Bool1, Bool2: boolean): integer; begin if Bool1 < Bool2 then Result := -1 else if Bool1 > Bool2 then Result := 1 else Result := 0; end; function CompareDouble(const Double1, Double2: Double): integer; begin if Double1 < Double2 then Result := -1 else if Double1 > Double2 then Result := 1 else Result := 0; end; procedure TCustomObjectList.Append(AItem: TCoreClassObject); begin Insert(Count, AItem); end; function TCustomSortedList.Add(AItem: TCoreClassObject): integer; begin if Sorted then begin Find(AItem, Result); Insert(Result, AItem); end else Result := inherited Add(AItem); end; function TCustomSortedList.AddUnique(Item: TCoreClassObject; RaiseError: boolean): integer; begin if Find(Item, Result) then begin if RaiseError then RaiseInfo(sAddingNonUniqueObject); Delete(Result); end; Insert(Result, Item); end; constructor TCustomSortedList.Create(AutoFreeObj_: boolean); begin inherited Create(AutoFreeObj_); FSorted := true; end; function TCustomSortedList.Find(Item: TCoreClassObject; out Index: integer): boolean; var AMin, AMax: integer; begin Result := false; if Sorted then begin // Find position for insert - binary method Index := 0; AMin := 0; AMax := Count; while AMin < AMax do begin Index := (AMin + AMax) div 2; case DoCompare(Items[Index], Item) of - 1: AMin := Index + 1; 0: begin Result := true; exit; end; 1: AMax := Index; end; end; Index := AMin; end else begin // If not a sorted list, then find it with the IndexOf() method Index := IndexOf(Item); if Index >= 0 then begin Result := true; exit; end; // Not found: set it to Count Index := Count; end; end; procedure TCustomSortedList.FindMultiple(Item: TCoreClassObject; out AIndex, ACount: integer); var IdxStart: integer; IdxClose: integer; begin if not Sorted then RaiseInfo(sListMustBeSorted); ACount := 0; // Find one if not Find(Item, AIndex) then exit; // Check upward from item IdxStart := AIndex; while (IdxStart > 0) and (DoCompare(Items[IdxStart - 1], Item) = 0) do dec(IdxStart); // Check downward from item IdxClose := AIndex; while (IdxClose < Count - 1) and (DoCompare(Items[IdxClose + 1], Item) = 0) do inc(IdxClose); // Result AIndex := IdxStart; ACount := IdxClose - IdxStart + 1; end; procedure TCustomSortedList.SetSorted(AValue: boolean); begin if AValue <> FSorted then begin FSorted := AValue; if FSorted then Sort; end; end; procedure TCustomSortedList.Sort; // local procedure QuickSort(iLo, iHi: integer); var Lo, Hi, Mid: Integer; begin Lo := iLo; Hi := iHi; Mid := (Lo + Hi) div 2; repeat while DoCompare(Items[Lo], Items[Mid]) < 0 do inc(Lo); while DoCompare(Items[Hi], Items[Mid]) > 0 do dec(Hi); if Lo <= Hi then begin // Swap pointers; Exchange(Lo, Hi); if Mid = Lo then Mid := Hi else if Mid = Hi then Mid := Lo; inc(Lo); dec(Hi); end; until Lo > Hi; if Hi > iLo then QuickSort(iLo, Hi); if Lo < iHi then QuickSort(Lo, iHi); end; // main begin if Count > 1 then begin QuickSort(0, Count - 1); end; FSorted := true; end; function TSortedList.DoCompare(Item1, Item2: TCoreClassObject): integer; begin if assigned(FOnCompare_) then Result := FOnCompare_(Item1, Item2, FCompareInfo) else if assigned(FCompareMethod) then Result := FCompareMethod(Item1, Item2, FCompareInfo) else Result := ComparePointer(Item1, Item2); end; function MakePoint2D(const AX, AY: Double): TPoing2D_; begin Result.X := AX; Result.Y := AY; end; procedure AddPoint2D(const A, B: TPoing2D_; var Result: TPoing2D_); begin Result.X := A.X + B.X; Result.Y := A.Y + B.Y; end; function SquaredLength2D(const APoint: TPoing2D_): Double; begin Result := Sqr(APoint.X) + Sqr(APoint.Y); end; function Length2D(const APoint: TPoing2D_): Double; begin Result := Sqrt(SquaredLength2D(APoint)); end; function PtsEqual2D(const A, B: TPoing2D_; const Eps: Double = 1E-12): boolean; begin Result := (abs(A.X - B.X) + abs(A.Y - B.Y)) <= Eps; end; function SquaredDist2D(const A, B: TPoing2D_): Double; begin Result := Sqr(A.X - B.X) + Sqr(A.Y - B.Y); end; function Dist2D(const A, B: TPoing2D_): Double; begin Result := Sqrt(SquaredDist2D(A, B)); end; function TaxicabDist2D(const A, B: TPoing2D_): Double; begin Result := abs(A.X - B.X) + abs(A.Y - B.Y); end; function CrossProduct2D(const Vector1, Vector2: TPoing2D_): Double; begin Result := Vector1.X * Vector2.Y - Vector1.Y * Vector2.X; end; function DotProduct2D(const Vector1, Vector2: TPoing2D_): Double; begin Result := Vector1.X * Vector2.X + Vector1.Y * Vector2.Y; end; procedure NormalizeVector2D(var AVector: TPoing2D_); var L: Double; begin L := Length2D(AVector); if L > 0 then begin L := 1 / L; AVector.X := AVector.X * L; AVector.Y := AVector.Y * L; end else begin // Avoid division by zero, return unity vec along X AVector.X := 1; AVector.Y := 0; end; end; procedure SubstractPoint2D(const A, B: TPoing2D_; var Result: TPoing2D_); begin Result.X := A.X - B.X; Result.Y := A.Y - B.Y; end; function Delta2D(const A, B: TPoing2D_): TPoing2D_; begin Result.X := B.X - A.X; Result.Y := B.Y - A.Y; end; function MidPoint2D(const A, B: TPoing2D_): TPoing2D_; begin Result.X := (A.X + B.X) * 0.5; Result.Y := (A.Y + B.Y) * 0.5; end; procedure Interpolation2D(const P1, P2: TPoing2D_; const xf: Double; var Result: TPoing2D_); var xf1: Double; begin xf1 := 1 - xf; Result.X := P1.X * xf1 + P2.X * xf; Result.Y := P1.Y * xf1 + P2.Y * xf; end; function PointToLineDist2DSqr(const P, P1, P2: TPoing2D_): Double; // Point-Line distance var q: Double; Pq: TPoing2D_; begin if PtsEqual2D(P1, P2) then begin // Point to point Result := SquaredDist2D(P, P1); exit; end; // Minimum q := ((P.X - P1.X) * (P2.X - P1.X) + (P.Y - P1.Y) * (P2.Y - P1.Y)) / (Sqr(P2.X - P1.X) + Sqr(P2.Y - P1.Y)); // Limit q to 0 <= q <= 1 if q < 0 then q := 0; if q > 1 then q := 1; // Distance Interpolation2D(P1, P2, q, Pq); Result := SquaredDist2D(P, Pq); end; function IntersectLines2D(const P1, P2, Q1, Q2: TPoing2D_; var R: TPoing2D_; var PosP, PosQ: Double; const Eps: Double = 1E-12): boolean; var DeltaP, DeltaQ: TPoing2D_; Num, Py, Px, DenP, DenQ: Double; begin Result := false; // Check numerator DeltaP := Delta2D(P1, P2); DeltaQ := Delta2D(Q1, Q2); Num := DeltaQ.Y * DeltaP.X - DeltaQ.X * DeltaP.Y; if abs(Num) < Eps then exit; // Denominators Result := true; Px := P1.X - Q1.X; Py := P1.Y - Q1.Y; DenP := DeltaQ.X * Py - DeltaQ.Y * Px; DenQ := DeltaP.X * Py - DeltaP.Y * Px; PosP := DenP / Num; PosQ := DenQ / Num; // intersection point Interpolation2D(P1, P2, PosP, R); end; procedure MirrorPointInLine(const P, Base, Dir: TPoing2D_; out R: TPoing2D_); // Mirror point P into the line with basepoint Base and direction Dir, put // result in R. Dir *must* be normalized. var Pb, Pp: TPoing2D_; Frac: Double; begin // P relative to base SubstractPoint2D(P, Base, Pb); // find projection on line Frac := DotProduct2D(Pb, Dir); Pp.X := Base.X + Dir.X * Frac; Pp.Y := Base.Y + Dir.Y * Frac; // Result is reflection of this point R.X := Pp.X * 2 - P.X; R.Y := Pp.Y * 2 - P.Y; end; function AngleBetweenVectors(A, B: TPoing2D_): Double; // Returns the angle between vector A and B in radians var C, S: Double; begin // Normalize A and B NormalizeVector2D(A); NormalizeVector2D(B); // Dotproduct = cosine, crossproduct = sine C := DotProduct2D(A, B); S := CrossProduct2D(A, B); // Sine = Y, Cosine = X, use arctan2 function Result := ArcTan2(S, C); end; function CircleFrom3PointsR2(const A, B, C: TPoing2D_; var Center: TPoing2D_; var R2, Den: Double): boolean; var A1, A2: Double; begin // Calculate circle center and radius (squared) Den := ((B.Y - C.Y) * (B.X - A.X) - (B.Y - A.Y) * (B.X - C.X)) * 2; A1 := (A.X + B.X) * (B.X - A.X) + (B.Y - A.Y) * (A.Y + B.Y); A2 := (B.X + C.X) * (B.X - C.X) + (B.Y - C.Y) * (B.Y + C.Y); // Make sure we don't divide by zero if abs(Den) > 1E-20 then begin Result := true; // Calculated circle center of circle through points a, b, c Center.X := (A1 * (B.Y - C.Y) - A2 * (B.Y - A.Y)) / Den; Center.Y := (A2 * (B.X - A.X) - A1 * (B.X - C.X)) / Den; // Squared radius of this circle R2 := SquaredDist2D(Center, A); end else begin // Co-linear, or close to it Result := false; end; end; function CircleFrom3Points(const A, B, C: TPoing2D_; var Center: TPoing2D_; var Radius: Double): boolean; var R2, Den: Double; begin Result := CircleFrom3PointsR2(A, B, C, Center, R2, Den); if Result then Radius := Sqrt(R2); end; function NormalFrom2Points_(const A, B: TPoing2D_): TPoing2D_; // Create the normal of a line from A to B, this is the vector perpendicular to // it (rotated 90 degrees clockwise), of unit length var D: TPoing2D_; begin D := Delta2D(A, B); // Turn 90 deg clockwise Result.X := D.Y; Result.Y := -D.X; NormalizeVector2D(Result); end; // Return the distance of P above the line through Base with Normal. If Dist is // negative, P lies below the line. function AboveBelowDist2D_(const Base, Normal, P: TPoing2D_): Double; // Return the distance of P above the line through Base with Normal. If Dist is // negative, P lies below the line. begin Result := DotProduct2D(Normal, Delta2D(Base, P)); end; function BetweenPointsTest2D_(const A, B, Point: TPoing2D_): integer; var AB, D: TPoing2D_; begin AB := Delta2D(A, B); D := Delta2D(A, Point); if DotProduct2D(D, AB) < 0 then begin Result := -1; exit; end; D := Delta2D(B, Point); if DotProduct2D(D, AB) > 0 then begin Result := 1; exit; end; Result := 0; end; procedure TVertex2D_.Assign(Source: TCoreClassPersistent); begin if Source is TVertex2D_ then begin FPoint := TVertex2D_(Source).FPoint; end else inherited Assign(Source); end; constructor TVertex2D_.Create; begin inherited Create; end; constructor TVertex2D_.CreateWithCoords(const AX, AY: Double); begin Create; FPoint.X := AX; FPoint.Y := AY; end; function TVertex2D_.GetPoint: PPoing2D_; begin Result := @FPoint; end; function TVertex2D_.GetX: Double; begin Result := FPoint.X; end; function TVertex2D_.GetY: Double; begin Result := FPoint.Y; end; procedure TVertex2D_.SetX(const Value: Double); begin FPoint.X := Value; end; procedure TVertex2D_.SetY(const Value: Double); begin FPoint.Y := Value; end; procedure TTriVertex2D_.Assign(Source: TCoreClassPersistent); begin if Source is TTriVertex2D_ then FTriangle := TTriVertex2D_(Source).FTriangle; inherited Assign(Source); end; function TTriVertex2D_.GetTriangle: TTriangle2D_; begin Result := FTriangle; end; procedure TTriVertex2D_.SetTriangle(const Value: TTriangle2D_); begin FTriangle := Value; end; function TVertex2DList_.GetItems(Index: integer): TVertex2D_; begin Result := inherited Items[index] as TVertex2D_; end; procedure TVertex2DList_.SetItems(Index: integer; const Value: TVertex2D_); begin inherited Items[index] := Value; end; procedure TSegment2D_.Assign(Source: TCoreClassPersistent); begin if Source is TSegment2D_ then begin FVertex1 := TSegment2D_(Source).FVertex1; FVertex2 := TSegment2D_(Source).FVertex2; end else inherited Assign(Source); end; procedure TSegment2D_.CalculateMetrics; var R: Double; begin if not assigned(FVertex1) or not assigned(FVertex2) then begin FCenter := cZero2D; FNormal := cZero2D; FSquaredEncroachRadius := 0; end else begin FCenter := MidPoint2D(FVertex1.FPoint, FVertex2.FPoint); R := Dist2D(FVertex1.FPoint, FCenter); // Take an encroach radius that is 10% bigger than the actual radius FSquaredEncroachRadius := Sqr(R * 1.1); // Normal FNormal := NormalFrom2Points_(FVertex1.FPoint, FVertex2.FPoint); end; FValidMetrics := true; end; constructor TSegment2D_.CreateWithVertices(AVertex1, AVertex2: TVertex2D_); begin Create; FVertex1 := AVertex1; FVertex2 := AVertex2; end; function TSegment2D_.GetCenter: TPoing2D_; begin if not FValidMetrics then CalculateMetrics; Result := FCenter; end; function TSegment2D_.GetNormal: TPoing2D_; begin if not FValidMetrics then CalculateMetrics; Result := FNormal; end; function TSegment2D_.GetSquaredEncroachRadius: Double; begin if not FValidMetrics then CalculateMetrics; Result := FSquaredEncroachRadius; end; function TSegment2D_.IntersectWith(ASegment: TSegment2D_): TVertex2D_; var R: TPoing2D_; PosP, PosQ: Double; begin Result := nil; if IntersectLines2D( FVertex1.FPoint, FVertex2.FPoint, ASegment.Vertex1.FPoint, ASegment.Vertex2.FPoint, R, PosP, PosQ) then begin if (PosP > 0) and (PosP < 1) and (PosQ > 0) and (PosQ < 1) then begin // OK we found an intersection, lying within both segments Result := TTriVertex2D_.CreateWithCoords(R.X, R.Y); end; end; end; procedure TSegment2D_.Invalidate; begin FValidMetrics := false; end; function TSegment2D_.IsVertexOnSegment(AVertex: TVertex2D_; APrecisionSqr: Double): boolean; begin Result := PointToLineDist2DSqr(AVertex.FPoint, FVertex1.FPoint, FVertex2.FPoint) <= APrecisionSqr; end; function TSegment2D_.PointEncroaches(const P: TPoing2D_): boolean; var C: TPoing2D_; begin C := GetCenter; Result := SquaredDist2D(C, P) < FSquaredEncroachRadius; end; procedure TSegment2D_.ReplaceVertex(OldVertex, NewVertex: TVertex2D_); begin if FVertex1 = OldVertex then SetVertex1(NewVertex); if FVertex2 = OldVertex then SetVertex2(NewVertex); end; procedure TSegment2D_.SetVertex1(const Value: TVertex2D_); begin FVertex1 := Value; FValidMetrics := false; end; procedure TSegment2D_.SetVertex2(const Value: TVertex2D_); begin FVertex2 := Value; FValidMetrics := false; end; function TSegment2DList_.GetItems(Index: integer): TSegment2D_; begin Result := inherited Items[index] as TSegment2D_; end; function TTriangle2D_.AngleCosine(Index: integer): Double; var D1, D2: TPoing2D_; begin Result := 0; if not(assigned(FVertices[0]) and assigned(FVertices[1]) and assigned(FVertices[2])) then exit; D1 := Delta2D(Vertices[Index].FPoint, Vertices[Index + 1].FPoint); D2 := Delta2D(Vertices[Index].FPoint, Vertices[Index + 2].FPoint); NormalizeVector2D(D1); NormalizeVector2D(D2); Result := DotProduct2D(D1, D2); end; function TTriangle2D_.Area: Double; var Pa, Pb, Pc: PPoing2D_; begin if assigned(FVertices[0]) and assigned(FVertices[1]) and assigned(FVertices[2]) then begin Pa := FVertices[0].Point; Pb := FVertices[1].Point; Pc := FVertices[2].Point; Result := CrossProduct2D(Delta2D(Pa^, Pb^), Delta2D(Pa^, Pc^)) * 0.5; end else Result := 0; end; procedure TTriangle2D_.CalculateMetrics; var Pa, Pb, Pc: PPoing2D_; begin if assigned(FVertices[0]) and assigned(FVertices[1]) and assigned(FVertices[2]) then begin Pa := FVertices[0].Point; Pb := FVertices[1].Point; Pc := FVertices[2].Point; // Center FCenter.X := (Pa^.X + Pb^.X + Pc^.X) * cOneThird; FCenter.Y := (Pa^.Y + Pb^.Y + Pc^.Y) * cOneThird; // Normals FNormals[0] := NormalFrom2Points_(Pa^, Pb^); FNormals[1] := NormalFrom2Points_(Pb^, Pc^); FNormals[2] := NormalFrom2Points_(Pc^, Pa^); end; // Set flag FValidMetrics := true; end; constructor TTriangle2D_.Create; begin inherited Create; FRegionIndex := -1; end; function TTriangle2D_.EdgeFromCenterTowardsPoint(const APoint: TPoing2D_): integer; var i: integer; C, Delta: TPoing2D_; CP1, CP2: Double; begin Result := -1; if not(assigned(FVertices[0]) and assigned(FVertices[1]) and assigned(FVertices[2])) then exit; C := GetCenter; Delta := Delta2D(C, APoint); CP2 := CrossProduct2D(Delta2D(C, Vertices[0].FPoint), Delta); for i := 0 to 2 do begin CP1 := CP2; CP2 := CrossProduct2D(Delta2D(C, Vertices[i + 1].FPoint), Delta); if (CP1 >= 0) and (CP2 < 0) then begin Result := i; exit; end; end; end; function TTriangle2D_.GetCenter: TPoing2D_; begin if not FValidMetrics then CalculateMetrics; Result := FCenter; end; function TTriangle2D_.GetNeighbours(Index: integer): TTriangle2D_; begin Result := FNeighbours[Index mod 3]; end; function TTriangle2D_.GetSegments(Index: integer): TSegment2D_; begin Result := nil; end; function TTriangle2D_.GetVertices(Index: integer): TVertex2D_; begin Result := FVertices[Index mod 3]; end; function TTriangle2D_.HitTest(const APoint: TPoing2D_): THitTestTriangle_; var i, Res: integer; P: array [0 .. 2] of PPoing2D_; Tol, TolSqr, TolOut, Dist: Double; begin Result := httNone; if not(assigned(FVertices[0]) and assigned(FVertices[1]) and assigned(FVertices[2])) then exit; if not FValidMetrics then CalculateMetrics; Tol := FMesh.FPrecision; TolSqr := FMesh.FPrecisionSqr; TolOut := Tol * 1E-3; // Sides check to determine insideness P[0] := FVertices[0].Point; P[1] := FVertices[1].Point; P[2] := FVertices[2].Point; // Check first side for i := 0 to 2 do begin Dist := AboveBelowDist2D_(P[i]^, FNormals[i], APoint); // More than TolOut away.. this point is outside this triangle if Dist > TolOut then exit; if abs(Dist) <= Tol then begin // Possibly on this line: check endpoints if SquaredDist2D(P[i]^, APoint) < TolSqr then begin // Yes on first vertex case i of 0: Result := httVtx0; 1: Result := httVtx1; 2: Result := httVtx2; end; exit; end; if SquaredDist2D(P[(i + 1) mod 3]^, APoint) < TolSqr then begin // Yes on second vertex case i of 0: Result := httVtx1; 1: Result := httVtx2; 2: Result := httVtx0; end; exit; end; // determine if between two vertices Res := BetweenPointsTest2D_(P[i]^, P[(i + 1) mod 3]^, APoint); if Res = 0 then begin // Indeed, between the vertices if Dist <= 0 then begin case i of 0: Result := httEdge0; 1: Result := httEdge1; 2: Result := httEdge2; end; end else begin case i of 0: Result := httClose0; 1: Result := httClose1; 2: Result := httClose2; end; end; exit; end; end; end; // Arriving here means inside Result := httBody; end; procedure TTriangle2D_.HookupNeighbours(TriangleA, TriangleB, TriangleC: TTriangle2D_); begin FNeighbours[0] := TriangleA; FNeighbours[1] := TriangleB; FNeighbours[2] := TriangleC; end; procedure TTriangle2D_.HookupVertices(VertexA, VertexB, VertexC: TVertex2D_); begin SetVertices(0, VertexA); SetVertices(1, VertexB); SetVertices(2, VertexC); end; procedure TTriangle2D_.Invalidate; begin FValidMetrics := false; end; procedure TTriangle2D_.InvalidateSegments; var i: integer; S: TSegment2D_; begin for i := 0 to 2 do begin S := Segments[i]; if assigned(S) then S.Invalidate; end; end; function TTriangle2D_.NeighbourIndex(ATriangle: TTriangle2D_): integer; var i: integer; begin Result := -1; for i := 0 to 2 do if FNeighbours[i] = ATriangle then begin Result := i; exit; end; end; procedure TTriangle2D_.ReplaceNeighbour(OldNeighbour, NewNeighbour: TTriangle2D_); var Idx: integer; begin Idx := NeighbourIndex(OldNeighbour); if Idx >= 0 then FNeighbours[Idx] := NewNeighbour; end; function TTriangle2D_.SegmentIndex(ASegment: TSegment2D_): integer; var i: integer; begin for i := 0 to 2 do if Segments[i] = ASegment then begin Result := i; exit; end; Result := -1; end; procedure TTriangle2D_.SetNeighbours(Index: integer; const Value: TTriangle2D_); begin FNeighbours[Index mod 3] := Value; end; procedure TTriangle2D_.SetSegments(Index: integer; const Value: TSegment2D_); begin // Default does nothing end; procedure TTriangle2D_.SetVertices(Index: integer; const Value: TVertex2D_); var Idx: integer; begin Idx := Index mod 3; if FVertices[Idx] <> Value then begin Value.Triangle := Self; FVertices[Idx] := Value; FValidMetrics := false; InvalidateSegments; end; end; function TTriangle2D_.SmallestAngleCosine: Double; var i: integer; D: array [0 .. 2] of TPoing2D_; ACos: Double; begin Result := 0; if not(assigned(FVertices[0]) and assigned(FVertices[1]) and assigned(FVertices[2])) then exit; for i := 0 to 2 do begin D[i] := Delta2D(Vertices[i].FPoint, Vertices[i + 1].FPoint); NormalizeVector2D(D[i]); end; for i := 0 to 2 do begin ACos := abs(DotProduct2D(D[i], D[(i + 1) mod 3])); if ACos > Result then Result := ACos; end; if Result > 1 then Result := 1; end; function TTriangle2D_.SquaredLongestEdgeLength: Double; var i: integer; L: Double; begin Result := 0; for i := 0 to 2 do begin L := SquaredDist2D(Vertices[i].FPoint, Vertices[i + 1].FPoint); if L > Result then Result := L; end; end; function TTriangle2D_.VertexIndex(AVertex: TVertex2D_): integer; var i: integer; begin for i := 0 to 2 do if FVertices[i] = AVertex then begin Result := i; exit; end; Result := -1; end; function TTriangle2DList_.GetItems(Index: integer): TTriangle2D_; begin Result := inherited Items[index] as TTriangle2D_; end; procedure TTriangleGroup2D_.AddTriangleAndEdge(ATriangle: TTriangle2D_; AEdge: integer); begin // adjust capacity if FCount >= FCapacity then begin FCapacity := FCount * 3 div 2 + 4; SetLength(FTriangles, FCapacity); SetLength(FEdges, FCapacity); end; FTriangles[FCount] := ATriangle; FEdges[FCount] := AEdge mod 3; inc(FCount); end; procedure TTriangleGroup2D_.Clear; begin FCount := 0; end; procedure TTriangleGroup2D_.Delete(AIndex: integer); var i: integer; begin if (AIndex < 0) or (AIndex >= FCount) then exit; for i := AIndex to FCount - 2 do begin FTriangles[i] := FTriangles[i + 1]; FEdges[i] := FEdges[i + 1]; end; dec(FCount); end; procedure TTriangleGroup2D_.Exchange(Index1, Index2: integer); var T: TTriangle2D_; E: integer; begin if (Index1 < 0) or (Index1 >= FCount) or (Index2 < 0) or (Index2 >= FCount) then exit; T := FTriangles[Index1]; FTriangles[Index1] := FTriangles[Index2]; FTriangles[Index2] := T; E := FEdges[Index1]; FEdges[Index1] := FEdges[Index2]; FEdges[Index2] := E; end; function TTriangleGroup2D_.GetEdges(Index: integer): integer; begin Result := FEdges[Index mod FCount]; end; function TTriangleGroup2D_.GetTriangles(Index: integer): TTriangle2D_; begin Result := FTriangles[Index mod FCount]; end; procedure TTriangleGroup2D_.InsertTriangleAndEdge(AIndex: integer; ATriangle: TTriangle2D_; AEdge: integer); var i: integer; begin // adjust capacity if FCount >= FCapacity then begin FCapacity := FCount * 3 div 2 + 4; SetLength(FTriangles, FCapacity); SetLength(FEdges, FCapacity); end; // Move up above index position for i := FCount downto AIndex + 1 do begin FTriangles[i] := FTriangles[i - 1]; FEdges[i] := FEdges[i - 1]; end; // Insert at index position FTriangles[AIndex] := ATriangle; FEdges[AIndex] := AEdge mod 3; inc(FCount); end; procedure TTriangleGroup2D_.SetEdges(Index: integer; const Value: integer); begin FEdges[Index mod FCount] := Value; end; { TTriangleFan2D_ } procedure TTriangleFan2D_.BuildTriangleFan(ABase: TTriangle2D_); var Triangle: TTriangle2D_; Idx: integer; begin // Reset count FCount := 0; Triangle := ABase; // scan anti-clockwise around center repeat Idx := Triangle.VertexIndex(FCenter); if Idx < 0 then RaiseInfo(sTriangleVertexHookupError); // add at end of list.. first one to be inserted is ABase, then any others // in anti-clockwise direction around center AddTriangleAndEdge(Triangle, Idx); // next triangle Triangle := Triangle.Neighbours[Idx + 2]; until (Triangle = ABase) or (Triangle = nil); if Triangle = nil then begin // in case we hit a border (no neighbours): we also scan clockwise from // base, and insert before rest of items. This will usually only happen // for meshes with holes or vertices at the borders of the mesh Idx := ABase.VertexIndex(FCenter); Triangle := ABase.Neighbours[Idx]; while Triangle <> nil do begin Idx := Triangle.VertexIndex(FCenter); // insert at first position in list InsertTriangleAndEdge(0, Triangle, Idx); Triangle := Triangle.Neighbours[Idx]; end; end; end; procedure TTriangleFan2D_.Clear; begin inherited Clear; FCenter := nil; end; function TTriangleFan2D_.GetVertices(Index: integer): TVertex2D_; var Idx: integer; begin Idx := Index mod Count; Result := FTriangles[Idx].Vertices[FEdges[Idx] + 1]; end; procedure TTriangleFan2D_.MoveToVertexAt(AIndex: integer); var Vertex: TVertex2D_; begin Vertex := Vertices[AIndex]; FCenter := Vertex; BuildTriangleFan(FTriangles[AIndex]); end; procedure TTriangleFan2D_.SetCenter(const Value: TVertex2D_); var Base: TTriangle2D_; begin FCenter := Value; if not assigned(Value) then exit; // to do: build triangle list Base := Value.Triangle; if not assigned(Base) then RaiseInfo(sNoTriangleForVertex); BuildTriangleFan(Base); end; function TTriangleFan2D_.TriangleIdxInDirection(const APoint: TPoing2D_): integer; var CP1, CP2: Double; DeltaVP: TPoing2D_; begin Result := -1; if FCount = 0 then exit; Result := 0; DeltaVP := Delta2D(FCenter.FPoint, APoint); CP2 := CrossProduct2D( // Edge at bottom side Delta2D(FCenter.FPoint, FTriangles[0].Vertices[FEdges[0] + 1].FPoint), // From center to vertex point DeltaVP); repeat CP1 := CP2; CP2 := CrossProduct2D( // Edge at top side Delta2D(FCenter.FPoint, FTriangles[Result].Vertices[FEdges[Result] + 2].FPoint), // From center to vertex point DeltaVP); // For CP1 we use "greater than or equal" and for CP2 "smaller than", this way // at least one of them should return the favour, even within machine precision if (CP1 >= 0) and (CP2 < 0) then // point lies above or on bottom edge, and below top edge, so this is the // triangle we are looking for exit; inc(Result); until Result = FCount; // arriving here means we didn't find it.. this is possible for borders Result := -1; end; function TTriangleFan2D_.TriangleInDirection(const APoint: TPoing2D_): TTriangle2D_; var Idx: integer; begin Idx := TriangleIdxInDirection(APoint); if Idx < 0 then Result := nil else Result := FTriangles[Idx]; end; function TTriangleFan2D_.VertexIndex(AVertex: TVertex2D_): integer; var i: integer; begin for i := 0 to Count - 1 do if GetVertices(i) = AVertex then begin Result := i; exit; end; Result := -1; end; function TTriangleChain2D_.BuildChain(AVertex1, AVertex2: TVertex2D_; var ASearchFan: TTriangleFan2D_): boolean; var Idx, Edge: integer; Fan: TTriangleFan2D_; Triangle, Previous: TTriangle2D_; Vertex: TVertex2D_; Delta12: TPoing2D_; begin Result := false; FVertex1 := AVertex1; FVertex2 := AVertex2; Clear; if not assigned(FVertex1) or not assigned(FVertex2) then exit; if FVertex1 = FVertex2 then begin Result := true; exit; end; // Searchfan to use if assigned(ASearchFan) then Fan := ASearchFan else Fan := TTriangleFan2D_.Create; try Fan.Center := FVertex1; Idx := Fan.VertexIndex(FVertex2); if Idx >= 0 then begin // Goody goody, we can stop because we directly found *one* triangle connecting // the two vertices AddTriangleAndEdge(Fan.Triangles[Idx], Fan.OutwardEdges[Idx] + 1); Result := true; exit; end; // No direct one, so we locate the triangle in the direction of Vertex 2 Idx := Fan.TriangleIdxInDirection(FVertex2.FPoint); // If there's none, we're doomed.. we cannot build the chain if Idx < 0 then exit; Delta12 := Delta2D(FVertex1.FPoint, FVertex2.FPoint); // First triangle and edge Triangle := Fan.Triangles[Idx]; Edge := (Fan.OutwardEdges[Idx] + 1) mod 3; AddTriangleAndEdge(Triangle, Edge); // Now we repeat, and keep adding triangle/edge combi's until we found Vertex 2 repeat // Move up one triangle, taking the neighbour on offending edge Previous := Triangle; Triangle := Previous.Neighbours[Edge]; // No triangle neighbour? We're doomed.. if not assigned(Triangle) then exit; // The edge on the new triangle that is offending Edge := Triangle.NeighbourIndex(Previous); // The vertex opposite of this one might be our end vertex Vertex := Triangle.Vertices[Edge + 2]; if Vertex = FVertex2 then begin // Yep! We found the end of the chain AddTriangleAndEdge(Triangle, Edge + 2); Result := true; exit; end; // On which side of the line is this opposite vertex? This way we determine // the offending edge if CrossProduct2D(Delta12, Delta2D(FVertex1.FPoint, Vertex.FPoint)) < 0 then begin // On right side, so the next offending edge is two vertices away Edge := Edge + 2; end else begin // On left side, so next offending edge is one vertex away Edge := Edge + 1; end; // Now we add the triangle, and the edge that is offending AddTriangleAndEdge(Triangle, Edge); until false; finally // Free searchfan if temporary if Fan <> ASearchFan then DisposeObject(Fan); end; end; { TTriMesh2D_ } function TTriMesh2D_.AbsoluteArea: Double; var i: integer; begin Result := 0; for i := 0 to Triangles.Count - 1 do Result := Result + abs(Triangles[i].Area); end; function TTriMesh2D_.BoundingBox(var AMin, AMax: TPoing2D_): boolean; var i: integer; P: PPoing2D_; begin if FVertices.Count > 0 then begin AMin := FVertices[0].Point^; AMax := FVertices[0].Point^; for i := 1 to FVertices.Count - 1 do begin P := FVertices[i].Point; if P^.X < AMin.X then AMin.X := P^.X; if P^.X > AMax.X then AMax.X := P^.X; if P^.Y < AMin.Y then AMin.Y := P^.Y; if P^.Y > AMax.Y then AMax.Y := P^.Y; end; Result := true; end else begin AMin := cZero2D; AMax := cZero2D; Result := false; end; end; procedure TTriMesh2D_.Clear; begin FVertices.Clear; FTriangles.Clear; FSegments.Clear; InitializeInfo; end; procedure TTriMesh2D_.ConvexHull; var ch: TConvexHull_; begin ch := TConvexHull_.Create; ch.MakeConvexHull(Self); DisposeObject(ch); end; constructor TTriMesh2D_.Create; begin inherited Create; FVertices := TVertex2DList_.Create(true); FTriangles := TTriangle2DList_.Create(true); FSegments := TSegment2DList_.Create(true); SetPrecision(cDefaultTriangulationPrecision); end; destructor TTriMesh2D_.Destroy; begin DisposeObject(FVertices); FVertices := nil; DisposeObject(FTriangles); FTriangles := nil; DisposeObject(FSegments); FSegments := nil; inherited Destroy; end; class function TTriMesh2D_.GetSegmentClass: TSegment2DClass_; begin Result := TSegment2D_; end; class function TTriMesh2D_.GetTriangleClass: TTriangle2DClass_; begin Result := TTriangle2D_; end; class function TTriMesh2D_.GetVertexClass: TVertex2DClass_; begin Result := TTriVertex2D_; end; procedure TTriMesh2D_.InitializeInfo; begin FSearchSteps := 0; end; function TTriMesh2D_.LocateClosestVertex(const APoint: TPoing2D_; AFan: TTriangleFan2D_): TVertex2D_; var i, BestIndex: integer; Fan: TTriangleFan2D_; IsClosest: boolean; CenterDist, FanDist, BestDist: Double; begin Result := nil; BestDist := 0; BestIndex := 0; if FTriangles.Count = 0 then exit; // Initialize triangle fan if assigned(AFan) then Fan := AFan else Fan := TTriangleFan2D_.Create; if Fan.Center = nil then Fan.Center := FTriangles[0].Vertices[0]; // Do search.. we use the taxicab distance here, that's faster than squared // distance, and more stable numerically repeat IsClosest := true; inc(FSearchSteps); CenterDist := TaxicabDist2D(Fan.Center.Point^, APoint); for i := 0 to Fan.Count - 1 do begin FanDist := TaxicabDist2D(Fan.Vertices[i].Point^, APoint); if FanDist < CenterDist then IsClosest := false; if (i = 0) or (FanDist < BestDist) then begin BestDist := FanDist; BestIndex := i; end; end; if not IsClosest then Fan.MoveToVertexAt(BestIndex); until IsClosest; // Result Result := Fan.Center; // Finalize triangle fan if Fan <> AFan then DisposeObject(Fan); end; function TTriMesh2D_.NewSegment: TSegment2D_; begin Result := GetSegmentClass.Create; end; function TTriMesh2D_.NewTriangle: TTriangle2D_; begin Result := GetTriangleClass.Create; Result.FMesh := Self; end; function TTriMesh2D_.NewVertex: TVertex2D_; begin Result := GetVertexClass.Create; end; procedure TTriMesh2D_.OptimizeForFEM(AVertices: TVertex2DList_); var i, Idx, IdxSeed, IdxLowest: integer; SL: TSortedList; Seed, Connected: TTriangle2D_; ConnectedIdx: array [0 .. 2] of integer; V: TVertex2D_; // local procedure MoveForward; var i: integer; begin // Move triangle Seed to Idx IdxSeed := FTriangles.IndexOf(Seed); FTriangles.Exchange(Idx, IdxSeed); inc(Idx); // Any vertices used and not yet in array are added for i := 0 to 2 do begin V := Seed.Vertices[i]; if assigned(V) and (AVertices.IndexOf(V) < 0) then AVertices.Add(V); end; end; // main begin AVertices.Clear; SL := TSortedList.Create(false); try // Sort the triangles by their left position SL.OnCompare := {$IFDEF FPC}@{$ENDIF FPC}TriangleCompareLeft; // Add all triangles to the sortlist for i := 0 to FTriangles.Count - 1 do SL.Add(FTriangles[i]); // Current swap index Idx := 0; Seed := nil; while SL.Count > 0 { Idx < FTriangles.Count - 2 } do begin // Seed triangle (the one we're working from) if not assigned(Seed) then begin Seed := TTriangle2D_(SL[0]); SL.Delete(0); MoveForward; end; // Find lowest connected triangle index in SL IdxLowest := FTriangles.Count; for i := 0 to 2 do ConnectedIdx[i] := SL.IndexOf(Seed.Neighbours[i]); for i := 0 to 2 do if (ConnectedIdx[i] >= 0) and (ConnectedIdx[i] < IdxLowest) then IdxLowest := ConnectedIdx[i]; // Did we find a connected triangle still existing in our sl? if IdxLowest < FTriangles.Count then Connected := TTriangle2D_(SL[IdxLowest]) else Connected := nil; // Do we have a connected triangle? if assigned(Connected) then begin // We have a connection.. do the exchange Seed := Connected; SL.Delete(IdxLowest); MoveForward; end else begin // No connection.. re-initialize seed Seed := nil; end; end; finally DisposeObject(SL); end; end; procedure TTriMesh2D_.RemoveNonSegments; var i: integer; S: TSegment2D_; begin for i := FSegments.Count - 1 downto 0 do begin S := FSegments[i]; if not assigned(S.Vertex1) or not assigned(S.Vertex2) or (S.Vertex1 = S.Vertex2) then FSegments.Delete(i); end; end; procedure TTriMesh2D_.SetPrecision(const Value: Double); begin if FPrecision <> Value then begin FPrecision := Value; FPrecisionSqr := Sqr(FPrecision); end; end; function TTriMesh2D_.SignedArea: Double; var i: integer; begin Result := 0; for i := 0 to Triangles.Count - 1 do Result := Result + Triangles[i].Area; end; function TTriMesh2D_.TriangleCompareLeft(Item1, Item2: TCoreClassObject; Info: pointer): integer; // compare two triangles and decide which one is most on the left var T1, T2: TTriangle2D_; begin T1 := TTriangle2D_(Item1); T2 := TTriangle2D_(Item2); Result := CompareDouble(T1.Center.X, T2.Center.X); end; type TMeshAccess = class(TTriMesh2D_) end; procedure TConvexHull_.AddSegment(Idx1, Idx2: integer); var S: TSegment2D_; begin S := TMeshAccess(FMesh).NewSegment; S.Vertex1 := FMesh.Vertices[Idx1]; S.Vertex2 := FMesh.Vertices[Idx2]; FMesh.Segments.Add(S); end; procedure TConvexHull_.AddVertexToHull(AVertex: TVertex2D_); var i, Idx, Count: integer; S, S1, S2: TSegment2D_; IdxFirst, IdxLast: integer; procedure DeleteSegmentRange(AStart, ACount: integer); begin // Deleting a segment range is a bit tricky, since the range can wrap around in the list while ACount > 0 do begin // We delete segments at AStart until that's at the end of the list, else // we delete from the 0 position of the list if AStart < FMesh.Segments.Count then FMesh.Segments.Delete(AStart) else FMesh.Segments.Delete(0); dec(ACount); end; end; // main begin // init Count := FMesh.Segments.Count; IdxFirst := -1; IdxLast := -1; // Loop through all segments, and find first and last that are violated for i := 0 to Count - 1 do begin S := FMesh.Segments[i]; if SegmentViolated(S, AVertex) then begin // OK, this segment isn't abided.. IdxFirst := i; // Find first one if i = 0 then begin Idx := Count - 1; S := FMesh.Segments[Idx]; while SegmentViolated(S, AVertex) do begin dec(Idx); S := FMesh.Segments[Idx]; end; IdxFirst := (Idx + 1) mod Count; end; // Find last one Idx := i + 1; S := FMesh.Segments[Idx mod Count]; while SegmentViolated(S, AVertex) do begin inc(Idx); S := FMesh.Segments[Idx mod Count]; end; IdxLast := Idx - 1; // Make sure to have a positive delta idx if IdxLast < IdxFirst then inc(IdxLast, Count); break; end; end; // The vertex fell within all segments, so it's already in the hull -> we can stop if IdxFirst = -1 then exit; if IdxFirst = IdxLast then begin // If first and last indices are equal, we must split up the segment S1 := FMesh.Segments[IdxFirst]; S2 := TMeshAccess(FMesh).NewSegment; S2.Vertex1 := AVertex; S2.Vertex2 := S1.Vertex2; S1.Vertex2 := AVertex; FMesh.Segments.Insert(IdxFirst + 1, S2); end else begin // Otherwise we move first segment's endpoint and last segment's startpoint // to the vertex, and remove intermediate segments S1 := FMesh.Segments[IdxFirst]; S2 := FMesh.Segments[IdxLast mod Count]; S1.Vertex2 := AVertex; S2.Vertex1 := AVertex; Count := IdxLast - IdxFirst - 1; if Count > 0 then DeleteSegmentRange(IdxFirst + 1, Count); end; end; function TConvexHull_.IsLeftOfLine(const V1, V2, AVertex: TVertex2D_): boolean; begin Result := CrossProduct2D(Delta2D(V1.Point^, AVertex.Point^), Delta2D(V1.Point^, V2.Point^)) > 0; end; procedure TConvexHull_.MakeConvexHull(AMesh: TTriMesh2D_); var i: integer; begin if not assigned(AMesh) then exit; FMesh := AMesh; // Start by clearing the segments FMesh.Segments.Clear; // We need at least 3 vertices if FMesh.Vertices.Count < 3 then exit; // Build initial 3 segments if IsLeftOfLine(FMesh.Vertices[0], FMesh.Vertices[1], FMesh.Vertices[2]) then begin // vertex 0, 1, 2 counterclockwise AddSegment(0, 1); AddSegment(1, 2); AddSegment(2, 0); end else begin // vertex 0, 1, 2 clockwise, so add 0-2, 2-1 and 1-0 AddSegment(0, 2); AddSegment(2, 1); AddSegment(1, 0); end; // Now add each of the other vertices in turn to the hull for i := 3 to FMesh.Vertices.Count - 1 do AddVertexToHull(FMesh.Vertices[i]); end; function TConvexHull_.SegmentViolated(ASegment: TSegment2D_; AVertex: TVertex2D_): boolean; begin Result := not IsLeftOfLine(ASegment.Vertex1, ASegment.Vertex2, AVertex); end; procedure TGraph2D_.Clear; begin FVertices.Clear; FSegments.Clear; end; constructor TGraph2D_.Create; begin inherited Create; FVertices := TVertex2DList_.Create; FSegments := TSegment2DList_.Create; end; destructor TGraph2D_.Destroy; begin DisposeObject(FSegments); FSegments := nil; DisposeObject(FVertices); FVertices := nil; inherited Destroy; end; procedure TGraph2D_.ReplaceVertex(OldVertex, NewVertex: TVertex2D_); var i: integer; begin for i := 0 to Segments.Count - 1 do Segments[i].ReplaceVertex(OldVertex, NewVertex); end; function TSegmentTriangle2D_.GetSegments(Index: integer): TSegment2D_; begin Result := FSegments[Index mod 3]; end; procedure TSegmentTriangle2D_.SetSegments(Index: integer; const Value: TSegment2D_); var Idx: integer; begin Idx := Index mod 3; if FSegments[Idx] <> Value then begin FSegments[Idx] := Value; // make sure to recalculate, because e.g. delaunay props change Invalidate; end; end; function TMeshRegionList_.GetItems(Index: integer): TMeshRegion_; begin Result := inherited Items[index] as TMeshRegion_; end; procedure TTriangulationMesh2D_.AddGraph(AGraph: TGraph2D_); var i, Idx, FirstVtx: integer; V: TVertex2D_; S: TSegment2D_; begin // Store first element indices FirstVtx := Vertices.Count; // Add vertices for i := 0 to AGraph.Vertices.Count - 1 do begin V := NewVertex; V.Assign(AGraph.Vertices[i]); Vertices.Add(V); end; // Add segments for i := 0 to AGraph.Segments.Count - 1 do begin S := NewSegment; S.Assign(AGraph.Segments[i]); // Now figure out which vertices this segment connects Idx := AGraph.Vertices.IndexOf(S.Vertex1); S.Vertex1 := Vertices[FirstVtx + Idx]; Idx := AGraph.Vertices.IndexOf(S.Vertex2); S.Vertex2 := Vertices[FirstVtx + Idx]; Segments.Add(S); end; end; function TTriangulationMesh2D_.AddSegmentToTriangulation(ASegment: TSegment2D_): boolean; var i, j, E1, E2: integer; Triangle, T1, T2: TTriangle2D_; CrossSegment: TSegment2D_; Vertex, V1, V2: TVertex2D_; // local procedure SplitAndAddSegment(AVertex: TVertex2D_); var NewS: TSegment2D_; begin NewS := TSegment2D_.Create; NewS.Vertex1 := AVertex; NewS.Vertex2 := ASegment.Vertex2; ASegment.Vertex2 := AVertex; Segments.Add(NewS); end; // main begin // Build a triangle chain from Vertex1 to Vertex2 Result := FSegmentChain.BuildChain(ASegment.Vertex1, ASegment.Vertex2, FSearchFan); // Do any of the offending edges have a vertex *on* the segment to-be-added? for i := 0 to FSegmentChain.Count - 2 do begin Triangle := FSegmentChain.Triangles[i]; V1 := Triangle.Vertices[FSegmentChain.Edges[i]]; V2 := Triangle.Vertices[FSegmentChain.Edges[i] + 1]; if ASegment.IsVertexOnSegment(V1, FPrecisionSqr) then begin // Yep, V1 lies on our segment SplitAndAddSegment(V1); Result := false; exit; end; if ASegment.IsVertexOnSegment(V2, FPrecisionSqr) then begin // Yep, V2 lies on our segment SplitAndAddSegment(V2); Result := false; exit; end; end; // Do any triangles in this chain contain a segment crossing our segment to-be-added? for i := 0 to FSegmentChain.Count - 2 do begin Triangle := FSegmentChain.Triangles[i]; CrossSegment := Triangle.Segments[FSegmentChain.Edges[i]]; if assigned(CrossSegment) then begin // Indeed, we must split this one: find intersection Vertex := ASegment.IntersectWith(CrossSegment); if not assigned(Vertex) then // We do have a cross segment but no intersection.. this should not happen RaiseInfo(sCrossSegmentIntersectionError); // Add this vertex to our list Vertices.Add(Vertex); // Insert the vertex into the triangulation, this will force the other // segment to split as well AddVertexToTriangulation(Vertex, nil); // Now split our segment. SplitAndAddSegment(Vertex); Result := false; exit; end; end; // If we have more than one triangle in the chain, we must reduce it by swapping // triangle pairs. The triangles removed from the chain are stored in FRemovals if FSegmentChain.Count > 1 then ReduceSegmentChain(FSegmentChain, FRemovals); // After reduction we hopefully have only one segment.. so now we can add // this segment if FSegmentChain.Count = 1 then begin T1 := FSegmentChain.Triangles[0]; E1 := FSegmentChain.Edges[0] + 2; T2 := T1.Neighbours[E1]; E2 := T2.NeighbourIndex(T1); T1.Segments[E1] := ASegment; T2.Segments[E2] := ASegment; DoExecutionStep('Add Segment'); end; // Now we will re-check the list of removed triangles for i := 0 to FRemovals.Count - 1 do for j := 0 to 2 do CheckTriangleWithEdge(FRemovals[i], j, nil); FRemovals.Clear; end; function TTriangulationMesh2D_.AddVertexToTriangulation(AVertex: TVertex2D_; Updates: TTriangle2DList_): boolean; var Triangle: TTriangle2D_; OldVertex: TVertex2D_; Status: THitTestTriangle_; begin Triangle := nil; Result := true; Status := HitTestTriangles(AVertex.Point^, Triangle, true); // If on the body of the triangle, we will split the triangle into 3 sub- // triangles. // If on one of the edges, split the triangle into 2, on the edge, as well // as the triangle neighbouring it. // If on one of the vertices (httVtx0..2), this means that an earlier triangle // was formed with one of the vertices very close to this one-to-be-added. // We will skip this vertex, return the hit vertex, and we will inform about that. case Status of httNone: begin // Deary, we didn't find any triangle! This situation should normally not // arise, unless the initial mesh is not large enough, or the mesh is at // its limit of numerical precision. We will simply skip the vertex. Result := false; exit; end; httBody: SplitTriangleBody(Triangle, AVertex, Updates); httVtx0 .. httVtx2: begin DoExecutionStep(PFormat('Vertex %d skipped (too close to another one)', [Vertices.IndexOf(AVertex)])); inc(FVertexSkipCount); case Status of httVtx0: OldVertex := Triangle.Vertices[0]; httVtx1: OldVertex := Triangle.Vertices[1]; httVtx2: OldVertex := Triangle.Vertices[2]; else OldVertex := nil; end; // In case a vertex lies too close to another one, we will not add this // vertex, but we must make sure any segments to-be-added do not use this // vertex but the new one ReplaceVertexInSegments(AVertex, OldVertex); Result := false; end; httEdge0: SplitTriangleEdge(Triangle, 0, AVertex, Updates); httEdge1: SplitTriangleEdge(Triangle, 1, AVertex, Updates); httEdge2: SplitTriangleEdge(Triangle, 2, AVertex, Updates); end; end; function TTriangulationMesh2D_.BruteForceHitTestTriangles( const APoint: TPoing2D_; var ATriangle: TTriangle2D_): THitTestTriangle_; var i: integer; begin Result := httNone; ATriangle := nil; for i := 0 to Triangles.Count - 1 do begin Result := Triangles[i].HitTest(APoint); if not(Result in [httNone, httClose0, httClose1, httClose2]) then begin ATriangle := Triangles[i]; exit; end; end; end; function TTriangulationMesh2D_.BuildTriangleFan(AList: TTriangle2DList_; AVertex: TVertex2D_): boolean; // local procedure FindRecursive(ABase: TTriangle2D_); var i: integer; N: TTriangle2D_; begin // loop through neighbours for i := 0 to 2 do begin N := ABase.Neighbours[i]; if not assigned(N) then continue; // does neighbour contain AVertex? if N.VertexIndex(AVertex) = -1 then continue; // Obviously.. check if we do not have it already if AList.IndexOf(N) >= 0 then continue; // Ok, new one.. add it AList.Add(N); FindRecursive(N); end; end; // main var i: integer; TriBase: TTriangle2D_; begin AList.Clear; Result := false; if not assigned(AVertex) then exit; TriBase := AVertex.Triangle; if not assigned(TriBase) or (TriBase.VertexIndex(AVertex) = -1) then begin TriBase := nil; // Hecky-decky: not a correct pointer.. try the brute force method to find // one for i := 0 to Triangles.Count - 1 do if Triangles[i].VertexIndex(AVertex) >= 0 then begin TriBase := Triangles[i]; break; end; if not assigned(TriBase) then exit; end; Result := true; AList.Add(TriBase); FindRecursive(TriBase); end; procedure TTriangulationMesh2D_.CheckTriangleWithEdge(ATriangle: TTriangle2D_; AEdge: integer; Updates: TTriangle2DList_); begin // default does nothing end; procedure TTriangulationMesh2D_.Clear; begin inherited Clear; FCornerPoints.Clear; FRemovals.Clear; FSearchFan.Clear; FSegmentChain.Clear; end; constructor TTriangulationMesh2D_.Create; begin inherited Create; FCornerPoints := TVertex2DList_.Create(true); FRegions := TMeshRegionList_.Create(true); FRemovals := TTriangle2DList_.Create(false); FSearchFan := TTriangleFan2D_.Create; FSegmentChain := TTriangleChain2D_.Create; end; destructor TTriangulationMesh2D_.Destroy; begin DisposeObject(FCornerPoints); FCornerPoints := nil; DisposeObject(FRegions); FRegions := nil; DisposeObject(FRemovals); FRemovals := nil; DisposeObject(FSearchFan); FSearchFan := nil; DisposeObject(FSegmentChain); FSegmentChain := nil; inherited Destroy; end; procedure TTriangulationMesh2D_.DetectRegions; var i, RIdx, Idx: integer; T, N: TTriangle2D_; S: TSegment2D_; R: TMeshRegion_; Borders: TTriangle2DList_; // recursive, local procedure FloodRegion(ATriangle: TTriangle2D_; AIndex: integer); var i: integer; N: TTriangle2D_; S: TSegment2D_; begin Borders.Remove(ATriangle); ATriangle.RegionIndex := AIndex; // Direct neighbours? for i := 0 to 2 do begin N := ATriangle.Neighbours[i]; S := ATriangle.Segments[i]; if assigned(N) then begin if N.RegionIndex >= 0 then continue; if assigned(S) then begin // There's a segment inbetween, we add this one to the border list if Borders.IndexOf(N) < 0 then Borders.Add(N); end else begin // A neighbour in the same region FloodRegion(N, AIndex); end; end; end; end; // main begin // Clear all regions and indices Regions.Clear; for i := 0 to Triangles.Count - 1 do Triangles[i].RegionIndex := -1; // Initial region with winding number 0 R := TMeshRegion_.Create; Regions.Add(R); RIdx := 0; // Find a seed triangle, this is any triangle which doesn't have all neighbours set T := nil; for i := 0 to Triangles.Count - 1 do begin T := Triangles[i]; Idx := T.NeighbourIndex(nil); if Idx >= 0 then begin S := T.Segments[Idx]; if assigned(S) then begin if S.Vertex1 = T.Vertices[i] then R.WindingNumber := 1 else R.WindingNumber := -1; end else begin R.WindingNumber := 0; R.IsOuterRegion := true; end; break; end; end; if not assigned(T) then exit; // Temporary border list Borders := TTriangle2DList_.Create(false); try // Keep on flooding regions until there are no more bordering triangles repeat // Flood the mesh region from the triangle with RIdx region index FloodRegion(T, RIdx); R := nil; // Do we have any borders? if Borders.Count > 0 then begin T := Borders[0]; for i := 0 to 2 do begin N := T.Neighbours[i]; S := T.Segments[i]; if assigned(N) and assigned(S) then begin if N.RegionIndex >= 0 then begin // OK, found a neighbour with region initialized.. we base our // new region on the relation here R := TMeshRegion_.Create; if S.Vertex1 = T.Vertices[i] then R.WindingNumber := Regions[N.RegionIndex].WindingNumber + 1 else R.WindingNumber := Regions[N.RegionIndex].WindingNumber - 1; Regions.Add(R); inc(RIdx); break; end; end; end; end; until (Borders.Count = 0) or (R = nil); finally DisposeObject(Borders); end; end; procedure TTriangulationMesh2D_.DoExecutionStep(const AMessage: SystemString); begin if assigned(FOnExecutionStep) then FOnExecutionStep(Self, AMessage); end; procedure TTriangulationMesh2D_.DoPhaseComplete(const AMessage: SystemString); begin if assigned(FOnPhaseComplete) then FOnPhaseComplete(Self, AMessage); end; procedure TTriangulationMesh2D_.DoStatus(const AMessage: SystemString); begin if assigned(FOnStatus) then FOnStatus(Self, AMessage); end; procedure TTriangulationMesh2D_.FinalizeInfo; begin // Calculation time FCalculationTime := (GetTimeTick - FTick) / 1000; end; class function TTriangulationMesh2D_.GetTriangleClass: TTriangle2DClass_; begin Result := TSegmentTriangle2D_; end; function TTriangulationMesh2D_.HitTestTriangles(const APoint: TPoing2D_; var ATriangle: TTriangle2D_; UseQuick: boolean): THitTestTriangle_; var Neighbour: TTriangle2D_; Closest: TVertex2D_; Edge: integer; begin Result := httNone; if UseQuick then begin // Use a quick-search to find a likely triangle as a basis Closest := LocateClosestVertex(APoint, FSearchFan); // FSearchFan is a vertex jumper ATriangle := FSearchFan.TriangleInDirection(APoint); if not assigned(ATriangle) then ATriangle := Closest.Triangle; if not assigned(ATriangle) and (Triangles.Count > 0) then ATriangle := Triangles[0]; end else begin // We skip the quicksearch if not assigned(ATriangle) then RaiseInfo('triangle must be assigned without quicksearch'); end; // no triangles? if not assigned(ATriangle) then exit; repeat // Hit-test the triangle Result := ATriangle.HitTest(APoint); inc(FHitTests); // Deal with close hits if Result in [httClose0 .. httClose2] then begin // Try neighbour on this side Neighbour := nil; case Result of httClose0: Neighbour := ATriangle.Neighbours[0]; httClose1: Neighbour := ATriangle.Neighbours[1]; httClose2: Neighbour := ATriangle.Neighbours[2]; end; if assigned(Neighbour) then begin ATriangle := Neighbour; Result := ATriangle.HitTest(APoint); inc(FHitTests); end; case Result of httClose0: Result := httEdge0; httClose1: Result := httEdge1; httClose2: Result := httEdge2; end; end; if Result <> httNone then break; // Find neighbouring triangle Edge := ATriangle.EdgeFromCenterTowardsPoint(APoint); if Edge = -1 then RaiseInfo('Unable to find direction'); ATriangle := ATriangle.Neighbours[Edge]; // No neighbour: we have ended up in "da middle of nawheere" if not assigned(ATriangle) then break; until Result <> httNone; end; procedure TTriangulationMesh2D_.InitializeInfo; begin inherited InitializeInfo; FVertexSkipCount := 0; FSplitEdgeCount := 0; FSplitBodyCount := 0; FHitTests := 0; FCalculationTime := 0; FTick := GetTimeTick; end; procedure TTriangulationMesh2D_.PostProcessMesh; begin // default does nothing end; procedure TTriangulationMesh2D_.PrepareMeshConstruction; const cGrowFactor = 0.2; var Delta: TPoing2D_; Tri1, Tri2: TTriangle2D_; begin // Calculate bounding box if not BoundingBox(FBBMin, FBBMax) then exit; // MeshMin / MeshMax Delta := Delta2D(FBBMin, FBBMax); FMeshMin.X := FBBMin.X - Delta.X * cGrowFactor; FMeshMin.Y := FBBMin.Y - Delta.Y * cGrowFactor; FMeshMax.X := FBBMax.X + Delta.X * cGrowFactor; FMeshMax.Y := FBBMax.Y + Delta.Y * cGrowFactor; // Add 4 vertices and 2 triangles bounding the mesh area FCornerPoints.Clear; FCornerPoints.Add(GetVertexClass.CreateWithCoords(FMeshMin.X, FMeshMin.Y)); FCornerPoints.Add(GetVertexClass.CreateWithCoords(FMeshMax.X, FMeshMin.Y)); FCornerPoints.Add(GetVertexClass.CreateWithCoords(FMeshMax.X, FMeshMax.Y)); FCornerPoints.Add(GetVertexClass.CreateWithCoords(FMeshMin.X, FMeshMax.Y)); Tri1 := NewTriangle; Tri2 := NewTriangle; Tri1.HookupVertices(FCornerPoints[2], FCornerPoints[0], FCornerPoints[1]); Tri1.Neighbours[0] := Tri2; Tri2.HookupVertices(FCornerPoints[0], FCornerPoints[2], FCornerPoints[3]); Tri2.Neighbours[0] := Tri1; Triangles.Add(Tri1); Triangles.Add(Tri2); DoExecutionStep('prepare mesh'); FAreaInitial := SignedArea; end; procedure TTriangulationMesh2D_.ReduceSegmentChain(AChain: TTriangleChain2D_; ARemovals: TTriangle2DList_); begin // default does nothing end; procedure TTriangulationMesh2D_.RemoveMeshConstruction(ARemovalStyle: TRemovalStyle_); var i: integer; T: TTriangle2D_; R: TMeshRegion_; MustRemove: boolean; begin DetectRegions; // If we are not going to delete anything.. then leave now if ARemovalStyle = rsNone then exit; for i := Triangles.Count - 1 downto 0 do begin T := Triangles[i]; if T.RegionIndex < 0 then continue; R := Regions[T.RegionIndex]; case ARemovalStyle of rsOutside: MustRemove := R.IsOuterRegion; rsEvenOdd: MustRemove := not odd(R.WindingNumber); rsNonZero: MustRemove := R.WindingNumber = 0; rsNegative: MustRemove := R.WindingNumber < 0; else MustRemove := false; end; // case if MustRemove then RemoveTriangleFromMesh(T); end; // Remove the 4 corner points with triangle fans FCornerPoints.Clear; FSearchFan.Clear; end; procedure TTriangulationMesh2D_.RemoveTriangleFromMesh(ATriangle: TTriangle2D_); var i, Idx: integer; N: TTriangle2D_; V: TVertex2D_; begin // Remove ATriangle from neighbour pointers for i := 0 to 2 do begin N := ATriangle.Neighbours[i]; if not assigned(N) then continue; Idx := N.NeighbourIndex(ATriangle); if Idx = -1 then continue; N.Neighbours[Idx] := nil; end; // Any vertex pointing at it should have it's pointer reset for i := 0 to 2 do begin V := ATriangle.Vertices[i]; if assigned(V) and (V.Triangle = ATriangle) then begin // Point the vertex to one of the neighbours that also shares this triangle if assigned(ATriangle.Neighbours[i]) then begin V.Triangle := ATriangle.Neighbours[i]; continue; end; if assigned(ATriangle.Neighbours[i + 2]) then begin V.Triangle := ATriangle.Neighbours[i + 2]; continue; end; // If there are no neighbours, just nil it.. the vertex is orphaned V.Triangle := nil; end; end; // Now remove the triangle from the principal list Triangles.Remove(ATriangle); end; procedure TTriangulationMesh2D_.ReplaceVertexInSegments(Old_, New_: TVertex2D_); var i, Idx: integer; begin // all segments containing OldVertex should point to NewVertex for i := 0 to Segments.Count - 1 do Segments[i].ReplaceVertex(Old_, New_); // we also remove OldVertex from our vertices list, by setting its index to nil Idx := Vertices.IndexOf(Old_); if Idx >= 0 then Vertices[Idx] := nil; end; procedure TTriangulationMesh2D_.SplitTriangleBody(ATriangle: TTriangle2D_; AVertex: TVertex2D_; Updates: TTriangle2DList_); var Tri0, Tri1, Tri2, N1, N2: TTriangle2D_; begin // We already found that APoint lies within ATriangle, now we split ATriangle // into 3 subtriangles inc(FSplitBodyCount); // The old triangle will be new triangle 0 Tri0 := ATriangle; // New triangle 1 & 2 Tri1 := NewTriangle; Tri2 := NewTriangle; // Set neighbour's pointers back N1 := Tri0.Neighbours[1]; N2 := Tri0.Neighbours[2]; if assigned(N1) then N1.ReplaceNeighbour(Tri0, Tri1); if assigned(N2) then N2.ReplaceNeighbour(Tri0, Tri2); // Setup neighbours Tri1.HookupNeighbours(N1, Tri2, Tri0); Tri2.HookupNeighbours(N2, Tri0, Tri1); Tri0.Neighbours[1] := Tri1; Tri0.Neighbours[2] := Tri2; // Setup vertices Tri1.HookupVertices(Tri0.Vertices[1], Tri0.Vertices[2], AVertex); Tri2.HookupVertices(Tri0.Vertices[2], Tri0.Vertices[0], AVertex); // must come after Tri0.Vertices[2] := AVertex; Triangles.Add(Tri1); Triangles.Add(Tri2); DoExecutionStep('split body'); // Check segments Tri1.Segments[0] := Tri0.Segments[1]; Tri2.Segments[0] := Tri0.Segments[2]; Tri0.Segments[1] := nil; Tri0.Segments[2] := nil; // Add to updates if assigned(Updates) then begin Updates.Add(Tri0); Updates.Add(Tri1); Updates.Add(Tri2); end; // Check these triangles.. in default triangulator this does nothing, but // descendants can override CheckTriangleWithEdge(Tri0, 0, Updates); CheckTriangleWithEdge(Tri1, 0, Updates); CheckTriangleWithEdge(Tri2, 0, Updates); end; procedure TTriangulationMesh2D_.SplitTriangleEdge(ATriangle: TTriangle2D_; AEdge: integer; AVertex: TVertex2D_; Updates: TTriangle2DList_); var Tri11, Tri12, Tri21, Tri22, N1, N2: TTriangle2D_; E1, E2: integer; Pv, Po, Pl, Pr: PPoing2D_; NegTest: boolean; S, NewS: TSegment2D_; begin // We found that AVertex lies *on* ATriangle's edge with index AEdge. Hence we // split ATriangle, and it's neighbour (if any). Tri11 := ATriangle; E1 := AEdge; E2 := -1; Tri21 := Tri11.Neighbours[E1]; if assigned(Tri21) then begin // Check edge consistency E2 := Tri21.NeighbourIndex(Tri11); if E2 = -1 then // this should not happen.. the integrity is breached RaiseInfo('edges do not match'); // Since the vertex to insert lays on ATriangle, it doesn't lay on the opposite // one. Therefore, we must check if the opposite triangles won't be negative // after creation Pv := AVertex.Point; Po := Tri21.Vertices[E2 + 2].Point; Pl := Tri11.Vertices[E1].Point; Pr := Tri11.Vertices[E1 + 1].Point; NegTest := false; if CrossProduct2D(Delta2D(Po^, Pv^), Delta2D(Po^, Pl^)) <= 0 then NegTest := true; if CrossProduct2D(Delta2D(Po^, Pr^), Delta2D(Po^, Pv^)) <= 0 then NegTest := true; if NegTest then begin // Oops! Indeed.. do a triangle body split instead SplitTriangleBody(ATriangle, AVertex, Updates); exit; end; inc(FSplitEdgeCount); // Split Tri11 and Tri21 Tri12 := NewTriangle; Tri22 := NewTriangle; // Set neighbour's pointers back N1 := Tri11.Neighbours[E1 + 1]; if assigned(N1) then N1.ReplaceNeighbour(Tri11, Tri12); N2 := Tri21.Neighbours[E2 + 1]; if assigned(N2) then N2.ReplaceNeighbour(Tri21, Tri22); // Setup neighbours Tri11.Neighbours[E1] := Tri22; Tri11.Neighbours[E1 + 1] := Tri12; Tri12.HookupNeighbours(Tri11, Tri21, N1); Tri21.Neighbours[E2] := Tri12; Tri21.Neighbours[E2 + 1] := Tri22; Tri22.HookupNeighbours(Tri21, Tri11, N2); // Setup vertices Tri12.HookupVertices(Tri11.Vertices[E1 + 2], AVertex, Tri11.Vertices[E1 + 1]); Tri11.Vertices[E1 + 1] := AVertex; Tri22.HookupVertices(Tri21.Vertices[E2 + 2], AVertex, Tri21.Vertices[E2 + 1]); Tri21.Vertices[E2 + 1] := AVertex; Triangles.Add(Tri12); Triangles.Add(Tri22); // Add to updates list if assigned(Updates) then begin Updates.Add(Tri11); Updates.Add(Tri12); Updates.Add(Tri21); Updates.Add(Tri22); end; end else begin // Split just Tri11 Tri12 := NewTriangle; Tri22 := nil; // Set neighbour's pointers back N1 := Tri11.Neighbours[E1 + 1]; if assigned(N1) then N1.ReplaceNeighbour(Tri11, Tri12); // Setup neighbours Tri11.Neighbours[E1 + 1] := Tri12; Tri12.HookupNeighbours(Tri11, nil, N1); // Setup vertices Tri12.HookupVertices(Tri11.Vertices[E1 + 2], AVertex, Tri11.Vertices[E1 + 1]); Tri11.Vertices[E1 + 1] := AVertex; Triangles.Add(Tri12); // Add to updates list if assigned(Updates) then begin Updates.Add(Tri11); Updates.Add(Tri12); end; end; // Correct segments: first the segment to split up (if any) S := Tri11.Segments[E1]; if assigned(S) then begin // Yeppers, split segment and add the new one. We also directly assign // the new segment to the triangles, so this new segment doesnt need to // be added explicitly to the mesh with AddSegment. NewS := NewSegment; if S.Vertex1 = Tri11.Vertices[E1] then begin // Segment same direction as Tri11 edge: NewS.Vertex1 := AVertex; NewS.Vertex2 := Tri12.Vertices[2]; S.Vertex2 := AVertex; end else begin // Segment opposite direction as Tri11 edge: NewS.Vertex1 := Tri12.Vertices[2]; NewS.Vertex2 := AVertex; S.Vertex1 := AVertex; end; Tri12.Segments[1] := NewS; if assigned(Tri21) then begin Tri21.Segments[E2] := NewS; Tri22.Segments[1] := S; end; // Add the new segment to our list Segments.Add(NewS); end; // Other segments: Tri12 takes over from Tri11 on one side S := Tri11.Segments[E1 + 1]; Tri11.Segments[E1 + 1] := nil; Tri12.Segments[2] := S; if assigned(Tri21) then begin // Tri22 takes over from Tri21 on one side S := Tri21.Segments[E2 + 1]; Tri21.Segments[E2 + 1] := nil; Tri22.Segments[2] := S; end; DoExecutionStep('split edge'); // Check triangles CheckTriangleWithEdge(Tri11, E1 + 2, Updates); CheckTriangleWithEdge(Tri12, 2, Updates); if assigned(Tri21) then begin CheckTriangleWithEdge(Tri21, E2 + 2, Updates); CheckTriangleWithEdge(Tri22, 2, Updates); end; end; procedure TTriangulationMesh2D_.Triangulate(ARemovalStyle: TRemovalStyle_); var i, j: integer; S: TSegment2D_; T: TTriangle2D_; begin // Reset info InitializeInfo; // Prepare mesh area PrepareMeshConstruction; DoPhaseComplete('Mesh Construction'); FSearchFan.Clear; // Add all vertices to the triangulation. Some vertices might get skipped if // they fall on top of another one, in that case the accompanying segment will // be updated trough ReplaceVertexInSegments for i := 0 to Vertices.Count - 1 do AddVertexToTriangulation(Vertices[i], nil); DoPhaseComplete('Vertex addition'); // Since segments might have been updated and not be functional any longer.. RemoveNonSegments; // Add all segments to the triangulation, creating a constrained triangulation. // We use a "while" loop because segments might be split and segments can be // inserted on the fly i := 0; while i < Segments.Count do begin AddSegmentToTriangulation(Segments[i]); inc(i); end; DoPhaseComplete('Segment addition'); // Remove the elements we added for construction if ARemovalStyle = rsNone then begin // If construction is left on, we must add segments to all nil neighbours // (because only segmented outside edges subdivide well for postprocessing) for i := 0 to Triangles.Count - 1 do begin T := Triangles[i]; for j := 0 to 2 do begin if T.Neighbours[j] = nil then begin S := NewSegment; S.Vertex1 := T.Vertices[j]; S.Vertex2 := T.Vertices[j + 1]; Segments.Add(S); T.Segments[j] := S; end; end; end; end else begin RemoveMeshConstruction(rsOutside); end; DoPhaseComplete('Perform removal'); // Do post processing (virtual) PostProcessMesh; if not(ARemovalStyle in [rsNone, rsOutside]) then begin RemoveMeshConstruction(ARemovalStyle); DoPhaseComplete('Remove fill-rule'); end; // finalize info FinalizeInfo; end; procedure TDelaunayTriangle2D_.CalculateMetrics; var Pa, Pb, Pc: PPoing2D_; Den, A1, A2, R: Double; begin inherited CalculateMetrics; // Calculate circle center and radius (squared) Pa := Vertices[0].Point; Pb := Vertices[1].Point; Pc := Vertices[2].Point; Den := ((Pb^.Y - Pc^.Y) * (Pb^.X - Pa^.X) - (Pb^.Y - Pa^.Y) * (Pb^.X - Pc^.X)) * 2; A1 := (Pa^.X + Pb^.X) * (Pb^.X - Pa^.X) + (Pb^.Y - Pa^.Y) * (Pa^.Y + Pb^.Y); A2 := (Pb^.X + Pc^.X) * (Pb^.X - Pc^.X) + (Pb^.Y - Pc^.Y) * (Pb^.Y + Pc^.Y); // Make sure we don't divide by zero if abs(Den) > 1E-20 then begin // Calculated circle center of circle through points a, b, c FCircleCenter.X := (A1 * (Pb^.Y - Pc^.Y) - A2 * (Pb^.Y - Pa^.Y)) / Den; FCircleCenter.Y := (A2 * (Pb^.X - Pa^.X) - A1 * (Pb^.X - Pc^.X)) / Den; // Squared radius of this circle // We use a radius that is a fraction smaller than the real radius (by // DelaunayPrecision) to allow miniscule infringement of the delaunay property. // This will avoid indecisiveness and endless swapping R := Dist2D(FCircleCenter, Pa^) - TDelaunayMesh2D_(FMesh).FDelaunayPrecision; if R < 0 then R := 0; FSquaredRadius := Sqr(R); end else begin FCircleCenter := Center; FSquaredRadius := 0; end; inc(TDelaunayMesh2D_(FMesh).FCircleCalcCount); end; function TDelaunayTriangle2D_.GetCircleCenter: TPoing2D_; begin if not FValidMetrics then CalculateMetrics; Result := FCircleCenter; end; function TDelaunayTriangle2D_.GetSquaredRadius: Double; begin if not FValidMetrics then CalculateMetrics; Result := FSquaredRadius; end; function TDelaunayTriangle2D_.IsDelaunay: boolean; var i, j: integer; N: TTriangle2D_; V: TVertex2D_; C: TPoing2D_; RSqr: Double; begin Result := false; // The center of the circle C := GetCircleCenter; // The square of the radius RSqr := FSquaredRadius; // Loop through neighbours for i := 0 to 2 do begin N := Neighbours[i]; // No neighbour, or a segment on this edge: skip if not assigned(N) or assigned(Segments[i]) then continue; for j := 0 to 2 do begin V := N.Vertices[j]; // Not one of the shared vertices? if (V = Vertices[i]) or (V = Vertices[i + 1]) then continue; // Determine the distance, and compare if SquaredDist2D(V.Point^, C) < RSqr then // Indeed, one of the opposite points is in, so we return "false" exit; end; end; // Ending up here means this triangle abides Delaunay Result := true; end; function TDelaunayTriangle2D_.VertexInCircle(AVertex: TVertex2D_): boolean; var C: TPoing2D_; begin C := GetCircleCenter; Result := SquaredDist2D(C, AVertex.Point^) <= FSquaredRadius; end; { TQualityTriangle2D_ } procedure TQualityTriangle2D_.CalculateMetrics; begin inherited CalculateMetrics; FQuality := SmallestAngleCosine; end; function TQualityTriangle2D_.EncroachedSegmentFromPoint(const APoint: TPoing2D_): TSegment2D_; var i: integer; S: TSegment2D_; SqrR: Double; begin Result := nil; SqrR := 0; for i := 0 to 2 do begin S := Segments[i]; if assigned(S) then begin if S.PointEncroaches(APoint) then begin if S.SquaredEncroachRadius > SqrR then begin Result := S; SqrR := S.SquaredEncroachRadius; end; end; end; end; end; function TQualityTriangle2D_.GetOffCenter: TPoing2D_; var SquaredBeta, L0Sqr, L1Sqr, L2Sqr, LMinSqr, HSqr, A: Double; EMin: integer; P, q, Delta, B: TPoing2D_; begin if not FValidMetrics then CalculateMetrics; // Squared edge lengths L0Sqr := SquaredDist2D(Vertices[0].Point^, Vertices[1].Point^); L1Sqr := SquaredDist2D(Vertices[1].Point^, Vertices[2].Point^); L2Sqr := SquaredDist2D(Vertices[2].Point^, Vertices[0].Point^); // Minimum squared edge length LMinSqr := L0Sqr; EMin := 0; if L1Sqr < LMinSqr then begin LMinSqr := L1Sqr; EMin := 1; end; if L2Sqr < LMinSqr then begin LMinSqr := L2Sqr; EMin := 2; end; // Squared beta factor SquaredBeta := FSquaredRadius / LMinSqr; // Offcenter: when the beta factor is higher than the required one, // we calculate the position of the offcenter such that the quality is exactly ok if SquaredBeta > TQualityMesh2D_(FMesh).FSquaredBeta then begin // Point B between PQ P := Vertices[EMin].Point^; q := Vertices[EMin + 1].Point^; HSqr := SquaredDist2D(P, q) * 0.25; // H = half of the distance between PQ B := MidPoint2D(P, q); A := Sqrt(FSquaredRadius - HSqr); Delta := Delta2D(B, FCircleCenter); NormalizeVector2D(Delta); // Off-center lies on point from B along carrier vector Delta, over distance a Result.X := B.X + A * Delta.X; Result.Y := B.Y + A * Delta.Y; end else // Otherwise, we use the circle center for the off-center Result := FCircleCenter; end; function TQualityTriangle2D_.GetQuality: Double; begin if not FValidMetrics then CalculateMetrics; Result := FQuality; end; function TQualityTriangle2D_.HasEncroachedSegment: boolean; var i: integer; S: TSegment2D_; begin Result := false; for i := 0 to 2 do begin S := Segments[i]; if assigned(S) then begin Result := S.PointEncroaches(Vertices[i + 2].Point^); if Result then exit; end; end; end; function TSortedTriangle2DList_.DoCompare(Item1, Item2: TCoreClassObject): integer; var T1, T2: TQualityTriangle2D_; begin T1 := TQualityTriangle2D_(Item1); T2 := TQualityTriangle2D_(Item2); // We compare quality and want the highest "quality" first (smallest angles), // so we invert Result := -CompareDouble(T1.Quality, T2.Quality); end; function TSortedTriangle2DList_.GetItems(Index: integer): TQualityTriangle2D_; begin Result := inherited Items[index] as TQualityTriangle2D_; end; procedure TEncroachItemList_.AddItem(AEncroacher, ATriangle: TTriangle2D_; ASegment: TSegment2D_); var i: integer; Item: TEncroachItem_; begin // Make sure we're unique for i := 0 to Count - 1 do begin Item := Items[i]; if (Item.Encroacher = AEncroacher) and (Item.Triangle = ATriangle) and (Item.Segment = ASegment) then exit; end; // If it doesn't exist yet, we create an item Item := TEncroachItem_.Create; Item.Encroacher := AEncroacher; Item.Triangle := ATriangle; Item.Segment := ASegment; Add(Item); end; function TEncroachItemList_.DoCompare(Item1, Item2: TCoreClassObject): integer; var E1, E2: TEncroachItem_; begin E1 := TEncroachItem_(Item1); E2 := TEncroachItem_(Item2); // We want the longest segment first, so we sort by squared radius, and invert Result := -CompareDouble(E1.Segment.SquaredEncroachRadius, E2.Segment.SquaredEncroachRadius); end; function TEncroachItemList_.GetItems(Index: integer): TEncroachItem_; begin Result := inherited Items[index] as TEncroachItem_; end; function TEncroachItemList_.IndexByTriangle(ATriangle: TTriangle2D_): integer; var i: integer; begin for i := 0 to Count - 1 do if Items[i].Triangle = ATriangle then begin Result := i; exit; end; Result := -1; end; procedure TEncroachItemList_.RemoveAllItemsWithSegment(ASegment: TSegment2D_); var i: integer; Item: TEncroachItem_; begin for i := Count - 1 downto 0 do begin Item := Items[i]; if Item.Segment = ASegment then Delete(i); end; end; procedure TEncroachItemList_.RemoveAllItemsWithTriangle(ATriangle: TTriangle2D_); var i: integer; Item: TEncroachItem_; begin for i := Count - 1 downto 0 do begin Item := Items[i]; if (Item.Encroacher = ATriangle) or (Item.Triangle = ATriangle) then Delete(i); end; end; { TDelaunayMesh2D_ } function TDelaunayMesh2D_.AllowSwapTriangles(T1, T2: TTriangle2D_; E1, E2: integer): boolean; var P10, P12, P20, P22: PPoing2D_; begin Result := false; // We do not allow a swap if there's a segment on the edge between the triangles if assigned(T1.Segments[E1]) then exit; // The corner vertices P10 := T1.Vertices[E1].Point; P12 := T1.Vertices[E1 + 2].Point; P20 := T2.Vertices[E2].Point; P22 := T2.Vertices[E2 + 2].Point; // Point P20 inside or on border? if CrossProduct2D(Delta2D(P22^, P20^), Delta2D(P22^, P12^)) <= 0 then exit; // Point P10 inside or on border? if CrossProduct2D(Delta2D(P12^, P10^), Delta2D(P12^, P22^)) <= 0 then exit; // Avoid creating triangles with no width if PointToLineDist2DSqr(P20^, P22^, P12^) < FPrecisionSqr then exit; if PointToLineDist2DSqr(P10^, P12^, P22^) < FPrecisionSqr then exit; // Arriving here means "all ok" Result := true; end; procedure TDelaunayMesh2D_.CheckTriangleWithEdge(ATriangle: TTriangle2D_; AEdge: integer; Updates: TTriangle2DList_); // local procedure CheckRecursive(ATriangle: TTriangle2D_; AEdge: integer); var T1, T2: TTriangle2D_; E1, E2: integer; begin // Two triangles T1 := ATriangle; T2 := ATriangle.Neighbours[AEdge]; if not assigned(T2) then exit; // Two edge indices E1 := AEdge; E2 := T2.NeighbourIndex(T1); if E2 = -1 then // this should not happen.. the integrity is breached RaiseInfo('edges do not match'); // Check if we need to swap these triangles if TDelaunayTriangle2D_(T1).VertexInCircle(T2.Vertices[E2 + 2]) or TDelaunayTriangle2D_(T2).VertexInCircle(T1.Vertices[E1 + 2]) then begin if not AllowSwapTriangles(T1, T2, E1, E2) then exit; // Yes we must swap SwapTriangles(T1, T2, E1, E2, Updates); // Recursive call CheckRecursive(T1, E1); CheckRecursive(T2, E2); CheckRecursive(T1, (E1 + 2) mod 3); CheckRecursive(T2, (E2 + 2) mod 3); end; end; // main begin CheckRecursive(ATriangle, AEdge); end; function TDelaunayMesh2D_.ForceDelaunay: integer; var i, j, NewCount: integer; T: TDelaunayTriangle2D_; begin Result := NonDelaunayTriangleCount; if Result = 0 then exit; repeat for i := 0 to Triangles.Count - 1 do begin T := TDelaunayTriangle2D_(Triangles[i]); if not T.IsDelaunay then begin // try in all directions for j := 0 to 2 do CheckTriangleWithEdge(T, j, nil); end; end; NewCount := NonDelaunayTriangleCount; DoExecutionStep('force delaunay cycle'); if (NewCount >= Result) or (NewCount = 0) then break; Result := NewCount; until false; Result := NewCount; end; class function TDelaunayMesh2D_.GetTriangleClass: TTriangle2DClass_; begin // This is the class we use Result := TDelaunayTriangle2D_; end; procedure TDelaunayMesh2D_.InitializeInfo; begin inherited InitializeInfo; FSwapCount := 0; FCircleCalcCount := 0; end; function TDelaunayMesh2D_.IsDelaunay: boolean; var i: integer; begin Result := false; for i := 0 to Triangles.Count - 1 do begin if not TDelaunayTriangle2D_(Triangles[i]).IsDelaunay then exit; end; Result := true; end; function TDelaunayMesh2D_.NonDelaunayTriangleCount: integer; var i: integer; begin Result := 0; for i := 0 to Triangles.Count - 1 do begin if not TDelaunayTriangle2D_(Triangles[i]).IsDelaunay then inc(Result); end; end; procedure TDelaunayMesh2D_.ReduceSegmentChain(AChain: TTriangleChain2D_; ARemovals: TTriangle2DList_); var i, Idx, BackupIdx, StartIdx, S1, S2: integer; T1, T2: TTriangle2D_; E1, E2: integer; V1, V2, P1, P2: TVertex2D_; Delta: TPoing2D_; MustExchange: boolean; // local procedure GetTrianglesAndEdges(AIndex: integer); begin // Triangles and edges for the pair T1 := AChain.Triangles[AIndex]; T2 := AChain.Triangles[AIndex + 1]; E1 := T1.NeighbourIndex(T2); E2 := T2.NeighbourIndex(T1); if (E1 < 0) or (E2 < 0) then RaiseInfo('Edges do not match'); // P1 and P2 vertex (left/right) for the pair P1 := T1.Vertices[E1 + 2]; P2 := T2.Vertices[E2 + 2]; // P1 below, on, or above line? if P1 = V1 then S1 := 0 else S1 := Sign(CrossProduct2D(Delta, Delta2D(V1.Point^, P1.Point^))); // P2 below, on, or above line? if P2 = V2 then S2 := 0 else S2 := Sign(CrossProduct2D(Delta, Delta2D(V1.Point^, P2.Point^))); end; // main begin if AChain.Count = 0 then exit; ARemovals.Clear; // Start and end vertex V1 := AChain.Triangles[0].Vertices[AChain.Edges[0] + 2]; Idx := AChain.Count - 1; V2 := AChain.Triangles[Idx].Vertices[AChain.Edges[Idx]]; // Delta Delta := Delta2D(V1.Point^, V2.Point^); StartIdx := 0; while AChain.Count > 1 do begin // Search for a swappable pair Idx := -1; BackupIdx := -1; for i := StartIdx to AChain.Count - 2 do begin // Triangles and edges for the pair GetTrianglesAndEdges(i); // Does it make sense to swap? if (S1 * S2 < 0) and AllowSwapTriangles(T1, T2, E1, E2) then begin // No, one point is above one is below, so the swap will not help us. // But in some cases, we *must* do this swap, if there are no others if BackupIdx < 0 then BackupIdx := i; continue; end; if AllowSwapTriangles(T1, T2, E1, E2) then begin // OK, this pair may be swapped Idx := i; break; end; end; StartIdx := 0; // Swap a pair that cannot be deleted, but might keep the algo going? if (Idx < 0) and (BackupIdx >= 0) then begin Idx := BackupIdx; GetTrianglesAndEdges(Idx); end; // If Idx isn't found, there are no more triangles to swap.. bad news if Idx < 0 then RaiseInfo('Cannot reduce triangle chain'); // We can swap this pair SwapTriangles(T1, T2, E1, E2, nil); // No deletion if below/above.. instead exchange them if sequence changed if S1 * S2 < 0 then begin if (Idx > 0) then MustExchange := T1.NeighbourIndex(AChain.Triangles[Idx - 1]) < 0 else MustExchange := T2.NeighbourIndex(AChain.Triangles[Idx + 2]) < 0; if MustExchange then AChain.Exchange(Idx, Idx + 1) else StartIdx := Idx + 1; continue; end; // Determine which one to take out of the list if (S1 > 0) or (S2 > 0) then begin // triangle 2 must go AChain.Delete(Idx + 1); ARemovals.Add(T2); end else begin // triangle 1 must go (also in case S1 = 0 and S2 = 0, aka the last 2) AChain.Delete(Idx); ARemovals.Add(T1); end; end; if AChain.Count = 1 then AChain.Edges[0] := AChain.Triangles[0].VertexIndex(V2); end; procedure TDelaunayMesh2D_.SetPrecision(const Value: Double); begin inherited SetPrecision(Value); // We set the delaunay precision as 1% of the precision FDelaunayPrecision := Value * 0.01; end; procedure TDelaunayMesh2D_.SwapTriangles(T1, T2: TTriangle2D_; E1, E2: integer; Updates: TTriangle2DList_); var N: TTriangle2D_; begin inc(FSwapCount); if assigned(Updates) then begin Updates.Add(T1); Updates.Add(T2); end; // Vertex triangle pointes T1.Vertices[E1 + 1].Triangle := T2; T2.Vertices[E2 + 1].Triangle := T1; // Vertex swap T1.Vertices[E1 + 1] := T2.Vertices[E2 + 2]; T2.Vertices[E2 + 1] := T1.Vertices[E1 + 2]; // Update neighbours' pointers back N := T1.Neighbours[E1 + 1]; if assigned(N) then N.ReplaceNeighbour(T1, T2); N := T2.Neighbours[E2 + 1]; if assigned(N) then N.ReplaceNeighbour(T2, T1); // Update our neighbour pointers T1.Neighbours[E1] := T2.Neighbours[E2 + 1]; T2.Neighbours[E2] := T1.Neighbours[E1 + 1]; T1.Neighbours[E1 + 1] := T2; T2.Neighbours[E2 + 1] := T1; // Update segments T1.Segments[E1] := T2.Segments[E2 + 1]; T2.Segments[E2] := T1.Segments[E1 + 1]; T1.Segments[E1 + 1] := nil; T2.Segments[E2 + 1] := nil; // Show result to user DoExecutionStep('swap triangles'); end; { TQualityMesh2D_ } procedure TQualityMesh2D_.BuildBadTriangleList; var i: integer; T: TQualityTriangle2D_; begin DoStatus('Building bad triangle list'); // Build the lists completely (first time) FBadTriangles.Clear; for i := 0 to Triangles.Count - 1 do begin T := TQualityTriangle2D_(Triangles[i]); if IsBadTriangle(T) then FBadTriangles.Add(T); end; end; procedure TQualityMesh2D_.Clear; begin inherited Clear; FBadTriangles.Clear; FEncroached.Clear; FUpdates.Clear; FSteinerPoints.Clear; end; constructor TQualityMesh2D_.Create; begin inherited Create; FBadTriangles := TSortedTriangle2DList_.Create(false); FEncroached := TEncroachItemList_.Create(true); FUpdates := TTriangle2DList_.Create(false); FSteinerPoints := TVertex2DList_.Create; SetMinimumAngle(cDefaultMinimumAngle); SetMinimumSegmentLength(cDefaultMinimumSegmentLength); end; function TQualityMesh2D_.DegenerateTriangleCount: integer; var i, j: integer; S: TSegment2D_; T: TQualityTriangle2D_; begin Result := 0; for i := 0 to Triangles.Count - 1 do begin T := TQualityTriangle2D_(Triangles[i]); for j := 0 to 2 do begin S := T.Segments[j]; if not assigned(S) then continue; if IsDegenerate(S) then begin inc(Result); break; end; end; end; end; destructor TQualityMesh2D_.Destroy; begin DisposeObject(FBadTriangles); FBadTriangles := nil; DisposeObject(FEncroached); FEncroached := nil; DisposeObject(FUpdates); FUpdates := nil; DisposeObject(FSteinerPoints); FSteinerPoints := nil; inherited Destroy; end; class function TQualityMesh2D_.GetTriangleClass: TTriangle2DClass_; begin Result := TQualityTriangle2D_; end; function TQualityMesh2D_.IsBadTriangle(ATriangle: TQualityTriangle2D_): boolean; begin // Minimum angle? Result := ATriangle.Quality > FMinimumAngleCos; if Result then exit; // Maximum element size? if FMaximumElementSize > 0 then Result := ATriangle.Area > FMaximumElementSize; end; function TQualityMesh2D_.IsDegenerate(ASegment: TSegment2D_): boolean; begin // Check: do not split triangles on a segment shorter than our precision Result := ASegment.SquaredEncroachRadius < FMinSegLengthSqr; end; procedure TQualityMesh2D_.LocalRefine(const X, Y, AMaximumElementSize: Double); var P: TPoing2D_; T: TTriangle2D_; // local function MustRefine(const P: TPoing2D_): boolean; var Res: THitTestTriangle_; begin Result := false; // Find the triangle under XY Res := HitTestTriangles(P, T, true); if Res = httNone then exit; Result := T.Area > AMaximumElementSize; end; // main begin P.X := X; P.Y := Y; T := nil; // repeat as long as there's work to do while MustRefine(P) do begin // Add the triangle to the bad list, so it gets split FBadTriangles.Add(T); // Now process the bad list ProcessBadTriangleList; end; end; function TQualityMesh2D_.MinimumAngleInMesh: Double; var i: integer; ACos: Double; T: TQualityTriangle2D_; begin Result := 0; for i := 0 to Triangles.Count - 1 do begin T := TQualityTriangle2D_(Triangles[i]); // Smallest angle cosine in triangle ACos := T.SmallestAngleCosine; if ACos > Result then Result := ACos; end; // Convert cosine to degrees Result := ArcCos(Result) * 180 / pi; end; procedure TQualityMesh2D_.PostProcessMesh; begin // The algorithm is as follows. We first build a list of all bad triangles. // We then check this list to see if these bad triangles encroach on any // segments. BuildBadTriangleList; ProcessBadTriangleList; DoStatus(PFormat('Current min. angle: %5.2f', [MinimumAngleInMesh])); DoPhaseComplete('Quality generation'); end; procedure TQualityMesh2D_.ProcessBadTriangleList; var i: integer; T: TQualityTriangle2D_; begin DoStatus('Processing bad triangle list'); // Test all bad triangles for encroachment for i := FBadTriangles.Count - 1 downto 0 do begin T := TQualityTriangle2D_(FBadTriangles[i]); // We call with TestOnly, so no triangles get actually split SplitBadTriangle(T, true); end; repeat // Split all encroached segments, this has priority. while FEncroached.Count > 0 do begin // Split encroached segment SplitEncroachedSegment(FEncroached[0]); // Deal with possible updates UpdateLists; end; // Next, any bad triangle get split (only the worst one, then recheck encroachment) if FBadTriangles.Count > 0 then begin T := TQualityTriangle2D_(FBadTriangles[0]); DoStatus(PFormat('Current min. angle %5.2f in bad triangles (%d)', [ArcCos(T.Quality) * 180 / pi, FBadTriangles.Count])); SplitBadTriangle(T, false); // Deal with possible updates UpdateLists; end; until (FEncroached.Count = 0) and (FBadTriangles.Count = 0); end; procedure TQualityMesh2D_.SetBeta(const Value: Double); begin FBeta := Value; FSquaredBeta := Sqr(FBeta); end; procedure TQualityMesh2D_.SetMinimumAngle(const Value: Double); var MinAngleRad: Double; begin if Value > 41.4 then RaiseInfo('Minimum value too high'); FMinimumAngleDeg := Value; MinAngleRad := Value * pi / 180; FMinimumAngleCos := cos(MinAngleRad); // Calculate related beta factor. We adjust it downwards *just* a bit to // avoid detecting inserted quality triangles as having angles too small. SetBeta(1 / (2 * sin(0.5 * MinAngleRad)) - 1E-5); end; procedure TQualityMesh2D_.SetMinimumSegmentLength(const Value: Double); begin FMinimumSegmentLength := Value; FMinSegLengthSqr := Sqr(FMinimumSegmentLength); end; procedure TQualityMesh2D_.SplitBadTriangle(ATriangle: TQualityTriangle2D_; TestOnly: boolean); var i: integer; TriFound, N: TTriangle2D_; P: TPoing2D_; S: TSegment2D_; V: TVertex2D_; Res: boolean; begin // Is the triangle worth splitting? if ATriangle.SquaredLongestEdgeLength < FMinSegLengthSqr then begin FBadTriangles.Remove(ATriangle); exit; end; // Get the off-center of this triangle P := ATriangle.OffCenter; repeat // Find the triangle at this point TriFound := ATriangle; HitTestTriangles(P, TriFound, false); if not assigned(TriFound) then begin // No triangle found: this means the offcenter is outside the triangulated area. // We cannot simply neglect this fact: we will try another point halfway between // the center and offcenter P := MidPoint2D(P, ATriangle.Center); end else break; until false; // We found a triangle. Do we encroach upon it? S := TQualityTriangle2D_(TriFound).EncroachedSegmentFromPoint(P); if not assigned(S) then begin // Also check neighbour triangles for i := 0 to 2 do begin N := TriFound.Neighbours[i]; if assigned(N) then begin S := TQualityTriangle2D_(N).EncroachedSegmentFromPoint(P); end; if assigned(S) then begin TriFound := N; break; end; end; end; // We encroached on segment S if it exists if assigned(S) then begin if IsDegenerate(S) then begin // We are only going to get better by splitting a degenerate segment which // we won't do.. so just remove this badboy FBadTriangles.Remove(ATriangle); exit; end else begin // Deary.. it does encroach on a non-degenerate triangle with segment. Our // triangle still stays bad, but we add the encroached segment to be split. FEncroached.AddItem(ATriangle, TriFound, S); exit; end; end; // As long as there are encroached segments, we don't add bad triangles if TestOnly or (FEncroached.Count > 0) then exit; // Arriving here means the triangle doesn't encroach on somebody, we can safely // split it (by *adding* the vertex, this will correctly split triangles on edges). V := GetVertexClass.CreateWithCoords(P.X, P.Y); FSteinerPoints.Add(V); Res := AddVertexToTriangulation(V, FUpdates); if not Res then begin FSteinerPoints.Delete(FSteinerPoints.Count - 1); FBadTriangles.Remove(ATriangle); end; end; procedure TQualityMesh2D_.SplitEncroachedSegment(AItem: TEncroachItem_); var T: TQualityTriangle2D_; S: TSegment2D_; V: TVertex2D_; C: TPoing2D_; begin // get the first encroached segment (usually there's only one) T := TQualityTriangle2D_(AItem.Triangle); S := AItem.Segment; // Check: do not split triangles that are degenerate if IsDegenerate(S) then begin FEncroached.RemoveAllItemsWithSegment(S); exit; end; // Split this segment: we must make a new vertex and add it to our steiner points C := S.Center; V := NewVertex; V.Point^ := C; FSteinerPoints.Add(V); // Now split the triangle SplitTriangleEdge(T, T.SegmentIndex(S), V, FUpdates); // And remove this segment from the list of encroached segments FEncroached.RemoveAllItemsWithSegment(S); end; procedure TQualityMesh2D_.UpdateLists; var i: integer; T: TQualityTriangle2D_; begin for i := 0 to FUpdates.Count - 1 do begin T := TQualityTriangle2D_(FUpdates[i]); if not assigned(T) then continue; FEncroached.RemoveAllItemsWithTriangle(T); if IsBadTriangle(T) then begin FBadTriangles.Extract(T); FBadTriangles.Add(T); // Also re-test the bad triangle SplitBadTriangle(T, true); end else FBadTriangles.Remove(T); end; FUpdates.Clear; end;