По "мотивам" - http://programmingmindstream.blogspot.ru/2016/09/1267.html
Всё те же "определители матрицы" и "векторные произведения"...
Всё те же "определители матрицы" и "векторные произведения"...
unit msLineF;
interface
uses
System.Types,
FMX.DUnit.msLog
;
type
Pixel = Single;
TmsPointF = record
public
P : TPointF;
constructor Create(const aPoint: TPointF); overload;
constructor Create(aX: Pixel; aY: Pixel); overload;
procedure ToLog(aLog: TmsLog);
class function Normalize(const aPt: TmsPointF): TmsPointF; static;
function N: TmsPointF;
function ToString: String;
property X: Pixel
read P.X
write P.X;
property Y: Pixel
read P.Y
write P.Y;
end;//TmsPointF
TmsLineF = record
public
A : TmsPointF;
B : TmsPointF;
procedure ToLog(aLog: TmsLog);
function ToString: String;
function Cross(const anOther: TmsLineF; out theCross: TmsPointF): Boolean;
// - пересечение линий
function SegmentsCross(const anOther: TmsLineF; out theCross: TmsPointF): Boolean;
// - пересечение отрезков
constructor Create(const aA: TmsPointF; const aB: TmsPointF); overload;
constructor Create(const aA: TPointF; const aB: TPointF); overload;
constructor Create(aAX, aAY: Pixel; aBX, aBY: Pixel); overload;
function dX: Pixel;
function dY: Pixel;
function Length: Pixel;
function ScalarMul(const anOther: TmsLineF): Pixel;
function CosA(const anOther: TmsLineF): Single;
end;//TmsLineF
TmsLineFPair = record
public
L1 : TmsLineF;
L2 : TmsLineF;
constructor Create(const aL1: TmsLineF; const aL2: TmsLineF);
procedure ToLog(aLog: TmsLog);
function ToString: String;
function Cross(out theCross: TmsPointF): Boolean;
// - пересечение линий
function SegmentsCross(out theCross: TmsPointF): Boolean;
// - пересечение отрезков
end;//TmsLineFPair
TmsLineFPairs = array of TmsLineFPair;
TmsRectF = record
public
R : TRectF;
constructor Create(const aR: TRectF);
function Cross(const anOther: TmsLineF; out theCross: TmsPointF): Boolean;
end;//TmsRectF
implementation
uses
System.SysUtils,
Math,
FMX.DUnit.msAppLog
;
// TmsPointF
constructor TmsPointF.Create(const aPoint: TPointF);
begin
P := aPoint;
end;
constructor TmsPointF.Create(aX: Pixel; aY: Pixel);
begin
Create(TPointF.Create(aX, aY));
end;
procedure TmsPointF.ToLog(aLog: TmsLog);
var
l_N : TmsPointF;
begin
l_N := Self.N;
aLog.ToLog('X:');
aLog.ToLog(FloatToStr(l_N.P.X));
aLog.ToLog('Y:');
aLog.ToLog(FloatToStr(l_N.P.Y));
end;
class function TmsPointF.Normalize(const aPt: TmsPointF): TmsPointF;
begin
Result := aPt;
end;
function TmsPointF.N: TmsPointF;
begin
Result := Normalize(Self);
end;
function TmsPointF.ToString: String;
var
l_P : TmsPointF;
begin
l_P := Self.N;
Result := FloatToStr(l_P.X) + '_' + FloatToStr(l_P.Y);
end;
// TmsLineF
procedure TmsLineF.ToLog(aLog: TmsLog);
begin
aLog.ToLog('dump line:');
aLog.ToLog('A:');
A.ToLog(aLog);
aLog.ToLog('B:');
B.ToLog(aLog);
end;
function TmsLineF.ToString: String;
begin
Result := A.ToString + '_' + B.ToString;
end;
function DoCross(const Self: TmsLineF; const anOther: TmsLineF; out theCross: TmsPointF): Boolean;
var
dXdY : Pixel;
dYdX : Pixel;
dYdY : Pixel;
begin//DoCross
Result := false;
Assert(not IsZero(Self.dY));
// - теперь тут можно будет вставить ЛЮБОЙ ДРУГОЙ алгоритм и посмотреть - "что будет"
dXdY := Self.dX * anOther.dY;
dYdX := Self.dY * anOther.dX;
dYdY := Self.dY * anOther.dY;
if IsZero(dXdY - dYdX) then
// - условие параллельности прямых
begin
// Self.ToLog(TmsAppLog.Instance);
// anOther.ToLog(TmsAppLog.Instance);
theCross.X := -1;
theCross.Y := -1;
Exit;
end;//IsZero(dXdY - dYdX)
Assert(not IsZero(dXdY - dYdX));
theCross.Y := (
dXdY * Self.A.Y -
dYdX * anOther.A.Y +
dYdY * (anOther.A.X - Self.A.X)
)
/
(dXdY - dYdX);
theCross.X := Self.A.X +
Self.dX * (theCross.Y - Self.A.Y)
/
Self.dY;
Result := true;
end;//DoCross
function TmsLineF.Cross(const anOther: TmsLineF; out theCross: TmsPointF): Boolean;
(*var
l_Angle : Single;*)
begin
Result := false;
theCross := TmsPointF.Create(High(Integer), High(Integer));
if IsZero(Self.Length) then
begin
theCross.X := 0;
if IsZero(anOther.Length) then
theCross.Y := 0;
Exit;
end//IsZero(Self.Length)
else
if IsZero(anOther.Length) then
begin
theCross.Y := 0;
Exit;
end;//IsZero(anOther.Length)
// Дальше нужно проверить параллельность прямых
(* l_Angle := ArcCos(Self.CosA(anOther));
if IsZero(l_Angle) OR SameValue(l_Angle, pi) OR SameValue(l_Angle, 2 * pi) then
begin
theCross.X := -1;
theCross.Y := -1;
Exit;
end;//IsZero(ArcCos(Self.CosA(anOther)))*)
if IsZero(Self.dY) then
begin
if IsZero(anOther.dX) then
begin
Result := true;
theCross.X := anOther.A.X;
theCross.Y := Self.A.Y;
Exit;
end//IsZero(anOther.dX)
else
begin
if IsZero(anOther.dY) then
begin
Result := false;
Exit;
end//IsZero(anOther.dY)
else
begin
Result := DoCross(anOther, Self, theCross);
Exit;
end;//IsZero(anOther.dY)
end;//IsZero(anOther.dX)
end//IsZero(Self.dY)
else
if IsZero(anOther.dY) then
begin
if IsZero(Self.dX) then
begin
Result := true;
theCross.X := Self.A.X;
theCross.Y := anOther.A.Y;
Exit;
end;//IsZero(Self.dX)
end;//IsZero(anOther.dY)
// Дальше можно по идее применять Мишин алгоритм:
Result := DoCross(Self, anOther, theCross);
end;
function TmsLineF.SegmentsCross(const anOther: TmsLineF; out theCross: TmsPointF): Boolean;
function Btwn(aValue, aB1, aB2: Pixel): Boolean;
var
l_Min : Pixel;
l_Max : Pixel;
begin//Btwn
l_Min := Min(aB1, aB2);
l_Max := Max(aB1, aB2);
Result := ((aValue >= l_Min) OR SameValue(aValue, l_Min)) and
((aValue <= l_Max) OR SameValue(aValue, l_Max));
end;//Btwn
begin
Result := Cross(anOther, theCross);
if Result then
begin
Result := Btwn(theCross.X, Self.A.X, Self.B.X) and
Btwn(theCross.X, anOther.A.X, anOther.B.X) and
Btwn(theCross.Y, Self.A.Y, Self.B.Y) and
Btwn(theCross.Y, anOther.A.Y, anOther.B.Y);
end;//Result
end;
constructor TmsLineF.Create(const aA: TmsPointF; const aB: TmsPointF);
begin
A := aA;
B := aB;
end;
constructor TmsLineF.Create(const aA: TPointF; const aB: TPointF);
begin
Create(TmsPointF.Create(aA.X, aA.Y), TmsPointF.Create(aB.X, aB.Y));
end;
constructor TmsLineF.Create(aAX, aAY: Pixel; aBX, aBY: Pixel);
begin
Create(TmsPointF.Create(aAX, aAY), TmsPointF.Create(aBX, aBY));
end;
function TmsLineF.dX: Pixel;
begin
Result := (B.X - A.X);
end;
function TmsLineF.dY: Pixel;
begin
Result := (B.Y - A.Y);
end;
function TmsLineF.Length: Pixel;
begin
Result := Sqrt(dX * dX + dY * dY);
end;
function TmsLineF.ScalarMul(const anOther: TmsLineF): Pixel;
begin
Result := Self.dX * anOther.dX + Self.dY * anOther.dY;
end;
function TmsLineF.CosA(const anOther: TmsLineF): Single;
begin
Result := Self.ScalarMul(anOther) / (Self.Length * anOther.Length);
end;
// TmsLineFPair
constructor TmsLineFPair.Create(const aL1: TmsLineF; const aL2: TmsLineF);
begin
L1 := aL1;
L2 := aL2;
end;
procedure TmsLineFPair.ToLog(aLog: TmsLog);
begin
aLog.ToLog('L1:');
L1.ToLog(aLog);
aLog.ToLog('L2:');
L2.ToLog(aLog);
end;
function TmsLineFPair.ToString: String;
begin
Result := L1.ToString + '_' + L2.ToString;
end;
function TmsLineFPair.Cross(out theCross: TmsPointF): Boolean;
begin
Result := L1.Cross(L2, theCross);
end;
function TmsLineFPair.SegmentsCross(out theCross: TmsPointF): Boolean;
begin
Result := L1.SegmentsCross(L2, theCross);
end;
// TmsRectF
constructor TmsRectF.Create(const aR: TRectF);
begin
R := aR;
end;
function TmsRectF.Cross(const anOther: TmsLineF; out theCross: TmsPointF): Boolean;
var
l_R : array [0..3] of TmsLineF;
l_L : TmsLineF;
begin
Result := true;
l_R[0] := TmsLineF.Create(TPointF.Create(R.Left, R.Top), TPointF.Create(R.Right, R.Top));
l_R[1] := TmsLineF.Create(TPointF.Create(R.Right, R.Top), TPointF.Create(R.Right, R.Bottom));
l_R[2] := TmsLineF.Create(TPointF.Create(R.Left, R.Bottom), TPointF.Create(R.Right, R.Bottom));
l_R[3] := TmsLineF.Create(TPointF.Create(R.Left, R.Top), TPointF.Create(R.Left, R.Bottom));
for l_L in l_R do
if l_L.SegmentsCross(anOther, theCross) then
Exit;
Result := false;
end;
end.
Пример использования:
unit msConnector;
interface
uses
System.Types,
msInterfaces,
msLine,
msLineWithArrow,
Data.DBXJSONReflect
;
type
TmsConnector = class(TmsLineWithArrow{TmsLine})
strict private
[JSONMarshalled(False)]
f_LeftShape : ImsShape;
[JSONMarshalled(False)]
f_RightShape : ImsShape;
f_UIDLeft : TmsShapeUID;
f_UIDRight : TmsShapeUID;
private
function pm_GetLeftShape: ImsShape;
property LeftShape : ImsShape
read pm_GetLeftShape;
function pm_GetRightShape: ImsShape;
property RightShape : ImsShape
read pm_GetRightShape;
protected
constructor CreateInner(const aShapeClass : ImsShapeClass; const aCtx: TmsMakeShapeContext); override;
procedure SetStartPoint(const aStartPoint: TPointF); override;
function pm_GetStartPoint: TPointF; override;
function pm_GetFinishPoint: TPointF; override;
function HitTest(const aPoint: TPointF; out theShape: ImsShape): Boolean; override;
procedure MoveBy(const aCtx: TmsMoveContext); override;
function EndTo(const aCtx: TmsEndShapeContext): Boolean; override;
class function BoundByContext(const aCtx: TmsMakeShapeContext): ImsShape;
public
class function IsConnectorLike: Boolean; override;
end;//TmsConnector
implementation
uses
msLineF,
msTotalShapesList
;
// TmsConnector
class function TmsConnector.BoundByContext(const aCtx: TmsMakeShapeContext): ImsShape;
begin
Result := aCtx.rShapesController.ShapeByPt(aCtx.rStartPoint);
if (Result <> nil) then
if Result.ShapeClass.IsLineLike then
// - линии не будем привязывать к линиям
Result := nil;
end;
constructor TmsConnector.CreateInner(const aShapeClass : ImsShapeClass; const aCtx: TmsMakeShapeContext);
begin
inherited;
if (aCtx.rShapesController <> nil) then
begin
f_LeftShape := BoundByContext(aCtx);
if (f_LeftShape <> nil) then
f_UIDLeft := f_LeftShape.UID;
end;//aCtx.rShapesController <> nil
end;
function TmsConnector.pm_GetLeftShape: ImsShape;
begin
if (f_LeftShape = nil) then
if not f_UIDLeft.IsNull then
f_LeftShape := TmsTotalShapesList.ShapeByUID(f_UIDLeft);
Result := f_LeftShape;
end;
function TmsConnector.pm_GetRightShape: ImsShape;
begin
if (f_RightShape = nil) then
if not f_UIDRight.IsNull then
f_RightShape := TmsTotalShapesList.ShapeByUID(f_UIDRight);
Result := f_RightShape;
end;
procedure TmsConnector.SetStartPoint(const aStartPoint: TPointF);
begin
inherited;
end;
function TmsConnector.pm_GetStartPoint: TPointF;
var
l_A : TPointF;
l_B : TPointF;
l_R : TmsPointF;
begin
if (LeftShape <> nil) then
begin
l_A := LeftShape.StartPoint;
if (RightShape <> nil) then
l_B := RightShape.StartPoint
else
l_B := inherited pm_GetFinishPoint;
if TmsRectF.Create(LeftShape.DrawBounds).Cross(TmsLineF.Create(l_A, l_B), l_R) then
Result := l_R.P
else
Result := l_A;
end//LeftShape <> nil
else
Result := inherited;
end;
function TmsConnector.pm_GetFinishPoint: TPointF;
var
l_A : TPointF;
l_B : TPointF;
l_R : TmsPointF;
begin
(* if (inherited pm_GetStartPoint = inherited pm_GetFinishPoint) then
begin
Result := inherited pm_GetStartPoint;
Exit;
end;//inherited pm_GetStartPoint = inherited pm_GetFinishPoint*)
if (RightShape <> nil) then
begin
l_B := RightShape.StartPoint;
if (LeftShape <> nil) then
l_A := LeftShape.StartPoint
else
l_A := inherited pm_GetStartPoint;
if TmsRectF.Create(RightShape.DrawBounds).Cross(TmsLineF.Create(l_A, l_B), l_R) then
Result := l_R.P
else
Result := l_B;
end//RightShape <> nil
else
if (LeftShape = nil) then
// - если нет ОБОИХ границ, то мы - "линия"
Result := inherited
else
Result := pm_GetStartPoint;
// - если есть только левая граница, то мы - "точка"
end;
function TmsConnector.HitTest(const aPoint: TPointF; out theShape: ImsShape): Boolean;
begin
Result := true;
if (LeftShape <> nil) AND LeftShape.HitTest(aPoint, theShape) then
Exit;
if (RightShape <> nil) AND RightShape.HitTest(aPoint, theShape) then
Exit;
Result := inherited;
end;
procedure TmsConnector.MoveBy(const aCtx: TmsMoveContext);
(*var
l_Shape : ImsShape;*)
begin
(* if (aCtx.rShapesController <> nil) then
begin
if (RightShape = nil) then
begin
if SamePoint(Self.FinishPoint, aCtx.rStartPoint) then
begin
RightShape := aCtx.rShapesController.ShapeByPt(Self.FinishPoint + aCtx.rDelta);
if Self.EQ(RightShape) then
RightShape := nil;
end;//SamePoint(Self.FinishPoint, aCtx.rStartPoint)
end;//RightShape = nil
end;//aCtx.rShapesController <> nil
if (LeftShape <> nil) AND SamePoint(aCtx.rStartPoint, Self.StartPoint) then
begin
LeftShape.MoveBy(aCtx);
Exit;
end;//LeftShape <> nil
if (RightShape <> nil) AND RightShape.HitTest(aCtx.rStartPoint + aCtx.rDelta, l_Shape) then
begin
RightShape.MoveBy(aCtx);
Exit;
end;//RightShape <> nil*)
inherited;
end;
function TmsConnector.EndTo(const aCtx: TmsEndShapeContext): Boolean;
begin
if (aCtx.rShapesController <> nil) then
begin
f_RightShape := BoundByContext(aCtx);
if (f_RightShape <> nil) then
f_UIDRight := f_RightShape.UID;
end;//aCtx.rShapesController <> nil
Result := inherited;
end;
class function TmsConnector.IsConnectorLike: Boolean;
begin
Result := true;
end;
end.
Комментариев нет:
Отправить комментарий