Как посмотреть, перекрываются ли две фигуры - PullRequest
4 голосов
/ 03 октября 2011

Я пытаюсь написать простое тестовое приложение firemonkey.

У меня есть форма с панелью (выравнивание: = alClient).
На форме 2 TCircle.Я установил TCircle.Dragmode: = dmAutomatic.

Я хотел бы перетащить круги и сделать что-нибудь, когда круги перекрываются.
Вопрос в том, что я не вижу в TCircle метода с именем overlap и не вижу события, вызываемого поперекрытия.Я перепробовал все события xxxxDrag, но это не помогло мне с тестированием.

Как я могу видеть, когда перетаскиваемая фигура перекрывается с другой формой?
Я ожидал, что одно из событий DragOver, DragEnter обнаружит это для меня, нопохоже, это не так.

Конечно, должен быть какой-то стандартный метод для этого в Firemonkey?

Пока что файл PAS выглядит так:

implementation

{$R *.fmx}

procedure TForm8.Circle1DragEnter(Sender: TObject; const Data: TDragObject;
  const Point: TPointF);
begin
  if Data.Source = Circle1 then Button1.Text:= 'DragEnter';

end;

procedure TForm8.Circle1DragOver(Sender: TObject; const Data: TDragObject;
  const Point: TPointF; var Accept: Boolean);
begin
  if (Data.Source = Circle2) then Button1.Text:= 'Circle2 drag';
end;

procedure TForm8.Circle2DragEnd(Sender: TObject);
begin
  Button1.Text:= 'DragEnd';
end;

procedure TForm8.Circle2DragEnter(Sender: TObject; const Data: TDragObject;
  const Point: TPointF);
begin
  Button1.Text:= 'DragEnter';
end;

procedure TForm8.Circle2DragLeave(Sender: TObject);
begin
  Button1.Text:= 'DragLeave';
end;

procedure TForm8.Circle2DragOver(Sender: TObject; const Data: TDragObject;
  const Point: TPointF; var Accept: Boolean);
begin
  if Data.Source = Circle2 then begin

    Button1.Text:= 'DragOver';
    Accept:= true;
  end;
end;

DFM выглядит примерно так:

object Form8: TForm8
  Left = 0
  Top = 0
  BiDiMode = bdLeftToRight
  Caption = 'Form8'
  ClientHeight = 603
  ClientWidth = 821
  Transparency = False
  Visible = False
  StyleLookup = 'backgroundstyle'
  object Panel1: TPanel
    Align = alClient
    Width = 821.000000000000000000
    Height = 603.000000000000000000
    TabOrder = 1
    object Button1: TButton
      Position.Point = '(16,16)'
      Width = 80.000000000000000000
      Height = 22.000000000000000000
      TabOrder = 1
      StaysPressed = False
      IsPressed = False
      Text = 'Button1'
    end
    object Circle1: TCircle
      DragMode = dmAutomatic
      Position.Point = '(248,120)'
      Width = 97.000000000000000000
      Height = 105.000000000000000000
      OnDragEnter = Circle1DragEnter
      OnDragOver = Circle1DragOver
    end
    object Circle2: TCircle
      DragMode = dmAutomatic
      Position.Point = '(168,280)'
      Width = 81.000000000000000000
      Height = 65.000000000000000000
      OnDragEnter = Circle2DragEnter
      OnDragLeave = Circle2DragLeave
      OnDragOver = Circle2DragOver
      OnDragEnd = Circle2DragEnd
    end
  end
end

Ответы [ 5 ]

16 голосов
/ 03 октября 2011

Общая проблема сложна и известна как обнаружение столкновений - вы можете использовать термин для поиска соответствующих алгоритмов.

Особый случай обнаружения столкновений кругов прост - просто вычислитерасстояние между центрами окружностей.Если полученное расстояние меньше суммы радиусов круга, круги перекрываются.

1 голос
/ 21 сентября 2013

Настоящим начало / настройка для обнаружения столкновения между TCircle, TRectangle и TRoundRect:

unit Unit1;

interface

uses
  System.SysUtils, System.Types, System.UITypes, System.Classes,
  FMX.Types, FMX.Controls, FMX.Forms, FMX.Objects, Generics.Collections, Math;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    Circle1: TCircle;
    Circle2: TCircle;
    Rectangle1: TRectangle;
    Rectangle2: TRectangle;
    RoundRect1: TRoundRect;
    RoundRect2: TRoundRect;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Panel1DragOver(Sender: TObject; const Data: TDragObject;
      const Point: TPointF; var Accept: Boolean);
    procedure Panel1DragDrop(Sender: TObject; const Data: TDragObject;
      const Point: TPointF);
  private
    FShapes: TList<TShape>;
    function CollidesWith(Source: TShape; const SourceCenter: TPointF;
      out Target: TShape): Boolean;
  end;

var
  Form1: TForm1;

implementation

{$R *.fmx}

function Radius(AShape: TShape): Single;
begin
  Result := Min(AShape.ShapeRect.Width, AShape.ShapeRect.Height) / 2;
end;

function TForm1.CollidesWith(Source: TShape; const SourceCenter: TPointF;
  out Target: TShape): Boolean;
var
  Shape: TShape;
  TargetCenter: TPointF;

  function CollidesCircleCircle: Boolean;
  begin
    Result :=
      TargetCenter.Distance(SourceCenter) <= (Radius(Source) + Radius(Target));
  end;

  function CollidesCircleRectangle: Boolean;
  var
    Dist: TSizeF;
    RHorz: TRectF;
    RVert: TRectF;
  begin
    Dist.cx := Abs(TargetCenter.X - SourceCenter.X);
    Dist.cy := Abs(TargetCenter.Y - SourceCenter.Y);
    RHorz := Target.ShapeRect;
    RHorz.Offset(Target.ParentedRect.TopLeft);
    RVert := RHorz;
    RHorz.Inflate(Radius(Source), 0);
    RVert.Inflate(0, Radius(Source));
    Result := RHorz.Contains(SourceCenter) or RVert.Contains(SourceCenter) or
      (Sqr(RVert.Width / 2 - Dist.cx) + Sqr(RHorz.Height / 2 - Dist.cy) <= 
        Sqr(Radius(Source)));
  end;

  function CollidesRectangleCircle: Boolean;
  var
    Dist: TSizeF;
    RHorz: TRectF;
    RVert: TRectF;
  begin
    Dist.cx := Abs(TargetCenter.X - SourceCenter.X);
    Dist.cy := Abs(TargetCenter.Y - SourceCenter.Y);
    RHorz := Source.ShapeRect;
    RHorz.Offset(Source.ParentedRect.TopLeft);
    RHorz.Offset(SourceCenter.Subtract(Source.ParentedRect.CenterPoint));
    RVert := RHorz;
    RHorz.Inflate(Radius(Target), 0);
    RVert.Inflate(0, Radius(Target));
    Result := RHorz.Contains(TargetCenter) or RVert.Contains(TargetCenter) or
      (Sqr(RVert.Width / 2 - Dist.cx) + Sqr(RHorz.Height / 2 - Dist.cy) <= 
        Sqr(Radius(Target)));
  end;

  function CollidesRectangleRectangle: Boolean;
  var
    Dist: TSizeF;
  begin
    Dist.cx := Abs(TargetCenter.X - SourceCenter.X);
    Dist.cy := Abs(TargetCenter.Y - SourceCenter.Y);
    Result := 
      (Dist.cx <= (Source.ShapeRect.Width + Target.ShapeRect.Width) / 2) and
      (Dist.cy <= (Source.ShapeRect.Height + Target.ShapeRect.Height) / 2); 
  end;

  function CollidesCircleRoundRect: Boolean;
  var
    Dist: TSizeF;
    R: TRectF;
  begin
    Dist.cx := Abs(TargetCenter.X - SourceCenter.X);
    Dist.cy := Abs(TargetCenter.Y - SourceCenter.Y);
    R := Target.ShapeRect;
    R.Offset(Target.ParentedRect.TopLeft);
    if R.Width > R.Height then
    begin
      Dist.cx := Dist.cx - (R.Width - R.Height) / 2;
      R.Inflate(-Radius(Target), Radius(Source));
    end
    else
    begin
      Dist.cy := Dist.cy - (R.Height - R.Width) / 2;
      R.Inflate(Radius(Source), -Radius(Target));
    end;
    Result := R.Contains(SourceCenter) or
      (Sqrt(Sqr(Dist.cx) + Sqr(Dist.cy)) <= (Radius(Source) + Radius(Target)));
  end;

  function CollidesRoundRectCircle: Boolean;
  var
    Dist: TSizeF;
    R: TRectF;
  begin
    Dist.cx := Abs(TargetCenter.X - SourceCenter.X);
    Dist.cy := Abs(TargetCenter.Y - SourceCenter.Y);
    R := Source.ShapeRect;
    R.Offset(Source.ParentedRect.TopLeft);
    R.Offset(SourceCenter.Subtract(Source.ParentedRect.CenterPoint));
    if R.Width > R.Height then
    begin
      Dist.cx := Dist.cx - (R.Width - R.Height) / 2;
      R.Inflate(-Radius(Source), Radius(Target));
    end
    else
    begin
      Dist.cy := Dist.cy - (R.Height - R.Width) / 2;
      R.Inflate(Radius(Target), -Radius(Source));
    end;
    Result := R.Contains(TargetCenter) or
      (Sqrt(Sqr(Dist.cx) + Sqr(Dist.cy)) <= (Radius(Source) + Radius(Target)));
  end;

  function CollidesRectangleRoundRect: Boolean;
  begin
    Result := False;
  end;

  function CollidesRoundRectRectangle: Boolean;
  begin
    Result := False;
  end;

  function CollidesRoundRectRoundRect: Boolean;
  begin
    Result := False;
  end;

  function Collides: Boolean;
  begin
    if (Source is TCircle) and (Target is TCircle) then
      Result := CollidesCircleCircle
    else if (Source is TCircle) and (Target is TRectangle) then
      Result := CollidesCircleRectangle
    else if (Source is TRectangle) and (Target is TCircle) then
      Result := CollidesRectangleCircle
    else if (Source is TRectangle) and (Target is TRectangle) then
      Result := CollidesRectangleRectangle
    else if (Source is TCircle) and (Target is TRoundRect) then
      Result := CollidesCircleRoundRect
    else if (Source is TRoundRect) and (Target is TCircle) then
      Result := CollidesRoundRectCircle
    else if (Source is TRectangle) and (Target is TRoundRect) then
      Result := CollidesRectangleRoundRect
    else if (Source is TRoundRect) and (Target is TRectangle) then
      Result := CollidesRoundRectRectangle
    else if (Source is TRoundRect) and (Target is TRoundRect) then
      Result := CollidesRoundRectRoundRect
    else
      Result := False;
  end;

begin
  Result := False;
  for Shape in FShapes do
  begin
    Target := Shape;
    TargetCenter := Target.ParentedRect.CenterPoint;
    Result := (Target <> Source) and Collides;
    if Result then
      Break;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  FShapes := TList<TShape>.Create;
  FShapes.AddRange([Circle1, Circle2, Rectangle1, Rectangle2, RoundRect1,
    RoundRect2]);
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  FShapes.Free;
end;

procedure TForm1.Panel1DragDrop(Sender: TObject; const Data: TDragObject;
  const Point: TPointF);
var
  Source: TShape;
begin
  Source := TShape(Data.Source);
  Source.Position.Point := PointF(Point.X - Source.Width / 2,
    Point.Y - Source.Height / 2);
end;

procedure TForm1.Panel1DragOver(Sender: TObject; const Data: TDragObject;
  const Point: TPointF; var Accept: Boolean);
var
  Source: TShape;
  Target: TShape;
begin
  Source := TShape(Data.Source);
  if CollidesWith(Source, Point, Target) then
    Caption :=  Format('Kisses between %s and %s', [Source.Name, Target.Name])
  else
    Caption := 'No love';
  Accept := True;
end;

end.
1 голос
/ 18 сентября 2013

Мне кажется, что существует слишком много возможных перестановок, чтобы легко решить эту проблему в общем и эффективно . Некоторые особые случаи могут иметь простое и эффективное решение: например, пересечение курсора мыши упрощается, если учитывать только одну точку на курсоре; была предоставлена ​​очень хорошая техника для кругов; многие регулярные формы также могут использовать собственные формулы для обнаружения столкновений.

Однако неправильные формы значительно усложняют задачу.

Один из вариантов - заключить каждую фигуру в воображаемый круг. Если эти круги перекрываются, то вы можете представить меньшие более плотные круги в окрестности исходного пересечения. Повторите расчеты с меньшими и меньшими кругами так часто, как это необходимо. Этот подход позволит вам выбрать компромисс между требованиями к обработке и точностью обнаружения.

Более простой и очень общий - хотя и несколько менее эффективный подход - рисовать каждую фигуру за пределами экрана, используя сплошные цвета и маску xor. Если после рисования будут найдены пиксели цвета xor, это будет означать столкновение.

1 голос
/ 18 ноября 2012

Хотя этому вопросу больше года, я недавно столкнулся с подобной проблемой. Благодаря небольшому исследованию TRectF (используется примитивами FMX и FM2), я придумал следующую очень простую функцию:

var
 aRect1, aRect2 : TRectF;
begin
  aRect1 := Selection1.AbsoluteRect;
  aRect2 := Selection2.AbsoluteRect;
  if System.Types.IntersectRect(aRect1,aRect2) then Result := True else Result := False;
end;

Самоочевидно, но если 2 прямоугольника / объекта пересекаются или перекрываются, то результат равен true.

Альтернатива - та же процедура, но уточненный код

var
 aRect1, aRect2 : TRectF;
begin
  aRect1 := Selection1.AbsoluteRect;
  aRect2 := Selection2.AbsoluteRect;
  result := System.Types.IntersectRect(aRect1,aRect2);
end;

Вам нужно будет поработать над ним, чтобы принять некоторые входные объекты (в моем случае я использовал TSelection, известные как Selection1 и Selection2) и, возможно, найти способ добавить смещение (взгляните на TControl.GetAbsoluteRect в FMX.Types), но теоретически он должен работать практически с любым примитивом или любым элементом управления.

Так же, как дополнительное примечание, для таких объектов используется множество TRectF;

  • AbsoluteRect
  • BoundsRect
  • LocalRect
  • UpdateRect (может не относиться к данной ситуации, требуется расследование)
  • ParentedRect
  • ClipRect
  • ChildrenRect

Важно использовать тот, который наиболее подходит для вашей ситуации (поскольку результаты будут сильно отличаться в каждом случае). В моем примере TSelection были дочерними элементами формы, поэтому использование AbsoluteRect было очень лучшим выбором (так как LocalRect не вернул правильные значения).

Реально, вы могли бы перебирать каждый дочерний компонент вашего родителя, чтобы иметь возможность выяснить, есть ли конфликт между любым из них и потенциально, вы могли бы создать функцию, которая точно скажет вам, какие из них сталкиваются (хотя для этого, вероятно, потребуется рекурсивная функция).

Если вам когда-нибудь понадобится разобраться с «базовой физикой», при которой обнаружение столкновений будет считаться единым (по крайней мере, в этом случае, это базовый уровень) в Firemonkey, тогда вам нужно обратиться к TRectF , В System.Types (XE3 и, скорее всего, XE2) встроено множество подпрограмм для автоматического решения этого вопроса, поэтому вы можете избежать математических операций, обычно связанных с этой проблемой.

Дополнительные примечания

Что-то, что я заметил, было то, что процедура выше не была очень точной и имела несколько пикселей. Одно из решений состоит в том, чтобы поместить вашу форму в родительский контейнер с выравниванием alClient, а затем с отступом в 5 пикселей по всем сторонам. Затем вместо измерения на TSelection.AbsoluteRect измерьте на AbsoluteRect.

дочернего объекта.

Например, я поместил TCircle внутри каждого TSelection, установил выравнивание окружностей на alClient, отступ до 5 на каждой стороне и изменил подпрограмму для работы с Circle1 и Circle2 в отличие от Selection1 и Selection2. Это оказалось точным в том смысле, что если бы сами круги не перекрывались (точнее, их области не перекрывались), то они не будут рассматриваться как сталкивающиеся до тех пор, пока края фактически не коснутся. Очевидно, что углы самих кругов представляют собой проблему, но вы, возможно, могли бы добавить еще один дочерний компонент внутри каждого круга с его видимостью, установленной в false, и с немного меньшими размерами, чтобы имитировать старый метод столкновения «Ограничивающий прямоугольник». обнаружение.

Пример приложения

Я добавил пример приложения с источником, показывающим выше. На вкладке 1 приведен полезный пример, а на второй вкладке приводится краткое объяснение того, как работает TRectF (и показаны некоторые ограничения, связанные с использованием визуального интерфейса, похожего на радар. Есть третья вкладка, демонстрирующая использование TBitmapListAnimation для создания анимированные картинки.

Обнаружение столкновения FMX - пример и источник

0 голосов
/ 20 сентября 2013

Думаю, нам пора кататься.

Одним из вариантов для этого является двумерная реализация алгоритма расстояния Гилберта-Джонсона-Керти .

Реализацию D можно найти здесь: http://code.google.com/p/gjkd/source/browse/

...