FastReport_2022_VCL/Source/frxAnaliticGeometry.pas
2024-01-01 16:13:08 +01:00

711 lines
19 KiB
ObjectPascal

{******************************************}
{ }
{ FastReport VCL }
{ Analitic Geometry }
{ }
{ Copyright (c) 1998-2021 }
{ by Fast Reports Inc. }
{ }
{******************************************}
unit frxAnaliticGeometry;
interface
uses
Types,
frxClass;
{$I frx.inc}
type
TDoublePoint = record
X, Y: Double;
end;
TDoubleRect = record
case Integer of
0: (Left, Top, Right, Bottom: Double);
1: (TopLeft, BottomRight: TDoublePoint);
end;
TPointArray = array of TPoint;
TDoubleArray = array of Double;
TDoublePointArray = array of TDoublePoint;
TDoublePointMatrix = array of TDoublePointArray;
type
TCircle = record
X, Y, Radius: Extended;
end;
TSegment = record
First, Second: TfrxPoint;
end;
function Circle(X, Y, Radius: Extended): TCircle;
function Segment(P1, P2: TPoint): TSegment; overload;
function Segment(x1, y1, x2, y2: Extended): TSegment; overload;
function Segment(P1, P2: TDoublePoint): TSegment; overload;
function Segment(P1, P2: TfrxPoint): TSegment; overload;
type
TMinDistance = class
private
protected
FValue: Extended;
FIndex: Integer;
FTreshold: Extended;
public
constructor Create(ATreshold: Extended = 0.0);
procedure Init(ATreshold: Extended);
procedure Add(AValue: Extended; AIndex: Integer);
function IsNear: Boolean;
function IsZero: Boolean;
property Index: Integer read FIndex;
end;
function DoublePoint(X, Y: Extended): TDoublePoint; overload;
function DoublePoint(P: TfrxPoint): TDoublePoint; overload;
function DoublePoint(P: TPoint): TDoublePoint; overload;
function DoubleRect(TL, BR: TfrxPoint): TDoubleRect; overload;
function DoubleRect(Left, Top, Right, Bottom: Double): TDoubleRect; overload;
function DoubleRect(R: TfrxRect): TDoubleRect; overload;
function DoubleRect(R: TRect): TDoubleRect; overload;
function DoubleRectExpand(DR: TDoubleRect; dX, dY: Double): TDoubleRect; overload;
function DoubleRectExpand(DR: TDoubleRect; d: Double): TDoubleRect; overload;
function ConstrainedDR(DR: TDoubleRect; ConstrainWidth, ConstrainHeight: Double): TDoubleRect;
function ToRect(DR: TDoubleRect): TRect;
function ToPoint(X, Y: Extended): TPoint; overload;
function ToPoint(frxPoint: TfrxPoint): TPoint; overload;
function ToFrxPoint(Point: TPoint): TfrxPoint;
function CenterRect(R: TfrxRect): TfrxPoint;
procedure InitRect(var R: TfrxRect; DP: TDoublePoint);
procedure ExpandRect(var R: TfrxRect; X, Y: Extended); overload;
procedure ExpandRect(var R: TfrxRect; DP: TDoublePoint); overload;
procedure ExpandRect(var R: TfrxRect; R1: TfrxRect); overload;
function RectWidth(R: TfrxRect): Extended; overload;
function RectWidth(R: TRect): Integer; overload;
function RectHeight(R: TfrxRect): Extended;
function Boundary(Value, MinValue, MaxValue: Extended): Extended;
function frxCanonicalRect(ALeft, ATop, ARight, ABottom: Extended): TfrxRect; overload;
function frxCanonicalRect(DR: TDoubleRect): TfrxRect; overload;
function IsPointInRect(P: TfrxPoint; R: TfrxRect): Boolean;
function IsInside(Value, Start, Finish: Extended): Boolean;
function IsInsideRect(R: TDoubleRect; P: TfrxPoint): Boolean;
function IsInsideEllipse(R: TDoubleRect; P: TfrxPoint): Boolean;
function IsInsidePolyline(Poly: TDoublePointArray; P: TfrxPoint): Boolean;
function IsInsidePolygon(Poly: TDoublePointArray; P: TfrxPoint): Boolean;
function IsInsideDiamond(R: TDoubleRect; P: TfrxPoint): Boolean;
function IsInsideMultiPolygon(Matrix: TDoublePointMatrix; P: TfrxPoint): Boolean;
function IsInsideMultiPolyline(Matrix: TDoublePointMatrix; P: TfrxPoint): Boolean;
function IsSegmentsIntersect(S1, S2: TSegment): Boolean;
function IsPointInPolygon(x, y: Extended; PolyPoints: TPointArray): Boolean;
function Distance(P1, P2: TfrxPoint): Extended; overload;
function Distance(P1: TDoublePoint; P2: TfrxPoint): Extended; overload;
function Distance(P: TfrxPoint; X, Y: Extended): Extended; overload;
function Distance(P: TPoint; X, Y: Extended): Extended; overload;
function DistancePolyline(Poly: TDoublePointArray; P: TfrxPoint): Extended;
function DistancePolygon(Poly: TDoublePointArray; P: TfrxPoint): Extended;
function DistanceSegment(Segment: TSegment; P: TfrxPoint): Extended;
function DistanceRect(Rect: TDoubleRect; P: TfrxPoint): Extended;
function DistanceDiamond(Rect: TDoubleRect; P: TfrxPoint): Extended;
function DistanceEllipse(Rect: TDoubleRect; P: TfrxPoint): Extended;
function DistancePicture(Rect: TDoubleRect; P: TfrxPoint): Extended;
function DistanceTemplate(Poly: TDoublePointArray; P: TfrxPoint): Extended;
function DistanceMultiPoint(Matrix: TDoublePointMatrix; P: TfrxPoint): Extended;
function DistanceMultiPolyline(Matrix: TDoublePointMatrix; P: TfrxPoint): Extended;
function DistanceMultiPolygon(Matrix: TDoublePointMatrix; P: TfrxPoint): Extended;
function IntersectionEllipse(Rect: TDoubleRect; P: TfrxPoint): TfrxPoint;
function IsEqual(P1, P2: TDoublePoint): Boolean;
const
Unknown = -1;
MinSelectDistance = 8;
implementation
uses
Math,
frxUtils;
const
MaxDistance = 1e38;
function Circle(X, Y, Radius: Extended): TCircle;
begin
Result.X := X;
Result.Y := Y;
Result.Radius := Radius;
end;
function Segment(P1, P2: TDoublePoint): TSegment;
begin
Result.First := frxPoint(P1.X, P1.Y);
Result.Second := frxPoint(P2.X, P2.Y);
end;
function Segment(x1, y1, x2, y2: Extended): TSegment;
begin
Result.First := frxPoint(x1, y1);
Result.Second := frxPoint(x2, y2);
end;
function Segment(P1, P2: TPoint): TSegment;
begin
Result.First := frxPoint(P1.X, P1.Y);
Result.Second := frxPoint(P2.X, P2.Y);
end;
function Segment(P1, P2: TfrxPoint): TSegment;
begin
Result.First := P1;
Result.Second := P2;
end;
function DoublePoint(P: TPoint): TDoublePoint;
begin
Result := DoublePoint(P.X, P.Y);
end;
function DoublePoint(P: TfrxPoint): TDoublePoint;
begin
Result := DoublePoint(P.X, P.Y);
end;
function DoublePoint(X, Y: Extended): TDoublePoint;
begin
Result.X := X;
Result.Y := Y;
end;
function DoubleRectExpand(DR: TDoubleRect; d: Double): TDoubleRect;
begin
Result := DoubleRectExpand(DR, d, d);
end;
function DoubleRectExpand(DR: TDoubleRect; dX, dY: Double): TDoubleRect;
begin
with DR do
Result := DoubleRect(Left - dX, Top - dY, Right + dX, Bottom + dY);
end;
function DoubleRect(Left, Top, Right, Bottom: Double): TDoubleRect;
begin
Result.Left := Left;
Result.Right := Right;
Result.Top := Top;
Result.Bottom := Bottom;
end;
function DoubleRect(R: TfrxRect): TDoubleRect;
begin
Result := DoubleRect(R.Left, R.Top, R.Right, R.Bottom);
end;
function DoubleRect(R: TRect): TDoubleRect;
begin
Result := DoubleRect(R.Left, R.Top, R.Right, R.Bottom);
end;
function DoubleRect(TL, BR: TfrxPoint): TDoubleRect;
begin
Result.TopLeft := DoublePoint(TL);
Result.BottomRight := DoublePoint(BR);
end;
function ConstrainedDR(DR: TDoubleRect; ConstrainWidth, ConstrainHeight: Double): TDoubleRect;
var
DRWidth, DRHeight, NewWidth, NewHeight: Double;
begin
DRWidth := DR.Right - DR.Left;
DRHeight := DR.Bottom - DR.Top;
NewWidth := DRHeight * (ConstrainWidth / ConstrainHeight);
NewHeight := DRWidth * (ConstrainHeight / ConstrainWidth);
if (DRHeight <= NewHeight) or (DRWidth >= NewWidth) then
DR.Right := DR.Left + NewWidth
else
DR.Bottom := DR.Top + NewHeight;
Result := DR;
end;
function ToRect(DR: TDoubleRect): TRect;
begin
with DR do
Result := Rect(Round(Left), Round(Top), Round(Right), Round(Bottom));
end;
function ToPoint(X, Y: Extended): TPoint;
begin
Result := Point(Round(X), Round(Y));
end;
function ToPoint(frxPoint: TfrxPoint): TPoint;
begin
with frxPoint do
Result := Point(Round(X), Round(Y));
end;
function ToFrxPoint(Point: TPoint): TfrxPoint;
begin
with Point do
Result := frxPoint(X, Y);
end;
function CenterRect(R: TfrxRect): TfrxPoint;
begin
with R do
Result := frxPoint((Left + Right) / 2, (Top + Bottom) / 2);
end;
procedure InitRect(var R: TfrxRect; DP: TDoublePoint);
begin
with DP do
R := frxRect(X, Y, X, Y);
end;
procedure ExpandRect(var R: TfrxRect; R1: TfrxRect);
begin
R.Left := Min(R.Left, R1.Left);
R.Top := Min(R.Top, R1.Top);
R.Right := Max(R.Right, R1.Right);
R.Bottom := Max(R.Bottom, R1.Bottom);
end;
procedure ExpandRect(var R: TfrxRect; DP: TDoublePoint);
begin
with DP do
ExpandRect(R, X, Y);
end;
procedure ExpandRect(var R: TfrxRect; X, Y: Extended);
begin
if R.Left > X then R.Left := X
else if R.Right < X then R.Right := X;
if R.Top > Y then R.Top := Y
else if R.Bottom < Y then R.Bottom := Y;
end;
function RectWidth(R: TRect): Integer;
begin
Result := R.Right - R.Left;
end;
function RectWidth(R: TfrxRect): Extended;
begin
Result := R.Right - R.Left;
end;
function RectHeight(R: TfrxRect): Extended;
begin
Result := R.Bottom - R.Top;
end;
function DistanceMultiPolygon(Matrix: TDoublePointMatrix; P: TfrxPoint): Extended;
var
iPart: Integer;
begin
Result := MaxDistance;
for iPart := 0 to High(Matrix) do
Result := Min(Result, DistancePolygon(Matrix[iPart], P));
end;
function DistanceMultiPolyline(Matrix: TDoublePointMatrix; P: TfrxPoint): Extended;
var
iPart: Integer;
begin
Result := MaxDistance;
for iPart := 0 to High(Matrix) do
Result := Min(Result, DistancePolyline(Matrix[iPart], P));
end;
function DistanceMultiPoint(Matrix: TDoublePointMatrix; P: TfrxPoint): Extended;
var
iPart, iPoint: Integer;
begin
Result := MaxDistance;
for iPart := 0 to High(Matrix) do
for iPoint := 0 to High(Matrix[iPart]) do
Result := Min(Result, Distance(Matrix[iPart, iPoint], P));
end;
function DistanceSegment(Segment: TSegment; P: TfrxPoint): Extended;
var
a, b, c, sp: Extended; // sp - semiperimeter
begin
a := Distance(Segment.First, Segment.Second);
b := Distance(Segment.First, P);
c := Distance(Segment.Second, P);
sp := (a + b + c) / 2;
if b * b >= a * a + c * c then
Result := c
else if c * c >= a * a + b * b then
Result := b
else
Result := Sqrt(sp * (sp - a) * (sp - b) * (sp - c)) * 2 / a;
end;
function DistancePolygon(Poly: TDoublePointArray; P: TfrxPoint): Extended;
begin
Result := Min(DistanceSegment(Segment(Poly[0], Poly[High(Poly)]), P),
DistancePolyline(Poly, P));
end;
function DistancePolyline(Poly: TDoublePointArray; P: TfrxPoint): Extended;
var
i: Integer;
begin
Result := DistanceSegment(Segment(Poly[0], Poly[1]), P);
for i := 2 to High(Poly) do
Result := Min(Result, DistanceSegment(Segment(Poly[i - 1], Poly[i]), P));
end;
function DistanceTemplate(Poly: TDoublePointArray; P: TfrxPoint): Extended;
var
i: Integer;
begin
Result := Distance(Poly[0], P);
for i := 1 to High(Poly) do
Result := Min(Result, Distance(Poly[i], P));
end;
function Distance(P1: TDoublePoint; P2: TfrxPoint): Extended;
begin
Result := Sqrt(Sqr(P1.X - P2.X) + Sqr(P1.Y - P2.Y));
end;
function Distance(P1, P2: TfrxPoint): Extended;
begin
Result := Sqrt(Sqr(P1.X - P2.X) + Sqr(P1.Y - P2.Y));
end;
function Distance(P: TfrxPoint; X, Y: Extended): Extended;
begin
Result := Sqrt(Sqr(P.X - X) + Sqr(P.Y - Y));
end;
function Distance(P: TPoint; X, Y: Extended): Extended;
begin
Result := Sqrt(Sqr(P.X - X) + Sqr(P.Y - Y));
end;
function DistanceRect(Rect: TDoubleRect; P: TfrxPoint): Extended;
var
Poly: TDoublePointArray;
begin
SetLength(Poly, 4);
with Rect do
begin
Poly[0] := DoublePoint(Left, Top);
Poly[1] := DoublePoint(Right, Top);
Poly[2] := DoublePoint(Right, Bottom);
Poly[3] := DoublePoint(Left, Bottom);
end;
Result := DistancePolygon(Poly, P);
end;
function IntersectionEllipse(Rect: TDoubleRect; P: TfrxPoint): TfrxPoint;
function Intersection(semi_major, semi_minor: Extended; p: TfrxPoint): TfrxPoint;
function hypot(x, y: Extended): Extended;
begin
Result := Sqrt(Sqr(x) + Sqr(y));
end;
var
px, py, t, a, b, x, y, ex, ey, rx, ry, qx, qy, r, q, delta_c, delta_t: Extended;
i: Integer;
begin // http://wet-robots.ghost.io/simple-method-for-distance-to-ellipse/
px := Abs(p.X);
py := Abs(p.Y);
t := Pi / 4;
a := semi_major;
b := semi_minor;
for i := 0 to 3 do
begin
x := a * Cos(t);
y := b * Sin(t);
ex := (a * a - b * b) * Power(Cos(t), 3) / a;
ey := (b * b - a * a) * Power(Sin(t), 3) / b;
rx := x - ex;
ry := y - ey;
qx := px - ex;
qy := py - ey;
r := hypot(ry, rx);
q := hypot(qy, qx);
delta_c := r * ArcSin((rx * qy - ry * qx) / (r * q));
delta_t := delta_c / Sqrt(a * a + b * b - x * x - y * y);
t := t + delta_t;
t := Min(Pi / 2, Max(0, t));
end;
Result := frxPoint(x * Sign(p.X), y * Sign(p.Y));
end;
var
a, b: Extended;
Center: TfrxPoint;
begin
with frxCanonicalRect(Rect) do
begin
Center := frxPoint((Left + Right) / 2, (Top + Bottom) / 2);
a := (Right - Left) / 2;
b := (Bottom - Top) / 2;
end;
with Intersection(a, b, frxPoint(P.X - Center.X, P.Y - Center.Y)) do
Result := frxPoint(X + Center.X, Y + Center.Y);
end;
function DistanceEllipse(Rect: TDoubleRect; P: TfrxPoint): Extended;
begin
Result := Distance(p, IntersectionEllipse(Rect, P));
end;
function DistancePicture(Rect: TDoubleRect; P: TfrxPoint): Extended;
begin
if IsPointInRect(P, frxCanonicalRect(Rect)) then
Result := 0
else
Result := DistanceRect(Rect, P);
end;
function DistanceDiamond(Rect: TDoubleRect; P: TfrxPoint): Extended;
var
Poly: TDoublePointArray;
begin
SetLength(Poly, 4);
with Rect do
begin
Poly[0] := DoublePoint((Left + Right) / 2, Top);
Poly[1] := DoublePoint(Right, (Top + Bottom) / 2);
Poly[2] := DoublePoint((Left + Right) / 2, Bottom);
Poly[3] := DoublePoint(Left, (Top + Bottom) / 2);
end;
Result := DistancePolygon(Poly, P);
end;
function IsPointInPolygon(x, y: Extended; PolyPoints: TPointArray): Boolean;
var
i1, i2: Integer;
begin
Result := False;
i2 := 0;
i1 := Length(PolyPoints) - 1;
while i1 >= 0 do
begin;
if not ((PolyPoints[i1].x < x) xor (x <= PolyPoints[i2].x)) then
if y - PolyPoints[i1].y < (x - PolyPoints[i1].x) *
(PolyPoints[i2].y - PolyPoints[i1].y) /
(PolyPoints[i2].x - PolyPoints[i1].x) then
Result := not Result;
i2 := i1;
i1 := i1-1;
end;
end;
function IsSegmentsIntersect(S1, S2: TSegment): Boolean;
const
Eps = 1e-6;
var
dxFirst, dyFirst, dxS1, dyS1, dxS2, dyS2, s_numer, t_numer, denom: Extended;
denomPositive: Boolean;
begin
Result := False;
dxS1 := S1.Second.X - S1.First.X;
dyS1 := S1.Second.Y - S1.First.Y;
dxS2 := S2.Second.X - S2.First.X;
dyS2 := S2.Second.Y - S2.First.Y;
denom := dxS1 * dyS2 - dxS2 * dyS1;
if Abs(denom) < Eps then // Collinear
Exit;
denomPositive := denom > 0;
dxFirst := S1.First.X - S2.First.X;
dyFirst := S1.First.Y - S2.First.Y;
s_numer := dxS1 * dyFirst - dyS1 * dxFirst;
if (s_numer < 0) = denomPositive then // No collision
Exit;
t_numer := dxS2 * dyFirst - dyS2 * dxFirst;
if (t_numer < 0) = denomPositive then // No collision
Exit;
if ((s_numer > denom) = denomPositive) or ((t_numer > denom) = denomPositive) then // No collision
Exit;
// Collision detected
Result := True;
end;
function frxCanonicalRect(ALeft, ATop, ARight, ABottom: Extended): TfrxRect;
begin
with Result do
begin
Left := Min(ALeft, ARight);
Top := Min(ATop, ABottom);
Right := Max(ALeft, ARight);
Bottom := Max(ATop, ABottom);
end;
end;
function IsEqual(P1, P2: TDoublePoint): Boolean;
begin
Result := (P1.X = P2.X) and (P1.Y = P2.Y);
end;
function IsInsideDiamond(R: TDoubleRect; P: TfrxPoint): Boolean;
var
Center: TDoublePoint;
Poly: TDoublePointArray;
begin
Center := DoublePoint((R.Left + R.Right) / 2, (R.Top + R.Bottom) / 2);
SetLength(Poly, 4);
Poly[0] := DoublePoint(Center.X, R.Top);
Poly[1] := DoublePoint(R.Right, Center.Y);
Poly[2] := DoublePoint(Center.X, R.Bottom);
Poly[3] := DoublePoint(R.Left, Center.Y);
Result := IsInsidePolygon(Poly, P);
end;
function IsInsideMultiPolyline(Matrix: TDoublePointMatrix; P: TfrxPoint): Boolean;
var
iPart: Integer;
begin
Result := False;
for iPart := 0 to High(Matrix) do
begin
Result := IsInsidePolyline(Matrix[iPart], P);
if Result then
Break;
end;
end;
function IsInsideMultiPolygon(Matrix: TDoublePointMatrix; P: TfrxPoint): Boolean;
var
iPart: Integer;
begin
Result := False;
for iPart := 0 to High(Matrix) do
begin
Result := IsInsidePolygon(Matrix[iPart], P);
if Result then
Break;
end;
end;
function IsInsidePolyline(Poly: TDoublePointArray; P: TfrxPoint): Boolean;
begin
Result := IsEqual(Poly[0], Poly[High(Poly)]) and IsInsidePolygon(Poly, P);
end;
function IsInsidePolygon(Poly: TDoublePointArray; P: TfrxPoint): Boolean;
var
i1, i2: Integer;
begin
Result := False;
i2 := 0;
i1 := Length(Poly) - 1;
while i1 >= 0 do
begin;
if not ((Poly[i1].x < P.X) xor (P.X <= Poly[i2].x)) then
if P.Y - Poly[i1].y < (P.X - Poly[i1].x) * (Poly[i2].y - Poly[i1].y) /
(Poly[i2].x - Poly[i1].x) then
Result := not Result;
i2 := i1;
i1 := i1 - 1;
end;
end;
function IsInsideEllipse(R: TDoubleRect; P: TfrxPoint): Boolean;
var
Center: TfrxPoint;
a, b, x, y: Extended;
begin
Center := frxPoint((R.Left + R.Right) / 2, (R.Top + R.Bottom) / 2);
a := R.Right - Center.X;
b := R.Bottom - Center.Y;
x := P.X - Center.X;
y := P.Y - Center.Y;
Result := Sqr(x) / Sqr(a) + Sqr(y) / Sqr(b) <= 1.0;
end;
function IsInsideRect(R: TDoubleRect; P: TfrxPoint): Boolean;
begin
Result := IsPointInRect(P, frxRect(R.Left, R.Top, R.Right, R.Bottom));
end;
function IsInside(Value, Start, Finish: Extended): Boolean;
begin
Result := (Value >= Start) and (Value <= Finish);
end;
function IsPointInRect(P: TfrxPoint; R: TfrxRect): Boolean;
begin
Result := IsInside(P.X, R.Left, R.Right) and
IsInside(P.Y, R.Top, R.Bottom);
end;
function frxCanonicalRect(DR: TDoubleRect): TfrxRect;
begin
with DR do
Result := frxCanonicalRect(Left, Top, Right, Bottom);
end;
function Boundary(Value, MinValue, MaxValue: Extended): Extended;
begin
if Value < MinValue then Result := MinValue
else if Value > MaxValue then Result := MaxValue
else Result := Value;
end;
{ TMinDistance }
procedure TMinDistance.Add(AValue: Extended; AIndex: Integer);
begin
if (FIndex = Unknown) or (FValue > AValue) then
begin
FValue := AValue;
FIndex := AIndex;
end;
end;
constructor TMinDistance.Create(ATreshold: Extended = 0.0);
begin
Init(ATreshold);
end;
procedure TMinDistance.Init(ATreshold: Extended);
begin
FIndex := Unknown;
FTreshold := ATreshold;
end;
function TMinDistance.IsNear: Boolean;
begin
Result := (FIndex <> Unknown) and (FValue <= FTreshold);
end;
function TMinDistance.IsZero: Boolean;
begin
Result := (FIndex <> Unknown) and (FValue = 0.0);
end;
end.