Przesuwanie komponentu od punktu A do B.

0

Witajcie.

Wyobraźmy sobie sytuacje, że wybraliśmy na formatce dwa punkty, oznaczmy je w sposób następujący: A(x1,y1), B(x2,y2).
Punkt A(x1,y1) wskazuje położenie początkowe (środek) względem formy pewnego komponentu, np Paintboxa.
Punkt B(x2,y2) wskazuje położenie końcowe tego komponentu.
Jak zapewne już się domyślacie, chodzi mi o płynny ruch komponentu z punktu A do B po lini prostej, najkrótszą drogą.

Mój sposób rozwiązania zagadnienia jest następujący:

Obliczam równanie prostej przechodzącej przez dwa punkty A i B. (Prosta zawiera przeciwprostokątną trójkąta (najkrótsza droga) o długości boków dx=x2-x1 oraz dy=y2-y1 co do wartości bezwględnej).
Równanie prostej ma postać y=ax+b.
gdzie y - wartość top komponentu, x - wartość left.
Obliczam współczynnik kierunkowy prostej: a = dx/dy, wyraz wolny b z równania b=y1-a*x1.
Komponentem poruszam w Timerze w sposób następujący:

Paintbox1.Left := Paintbox1.Left + 1; // +1, -1 w zależności od położenia punktów.
Paintbox1.Top := a * Paintbox1.Left  + b;

Prawie wszystko działa tak, jak chciałem. Obieram dowolny punkt B i komponent porusza się wedle wyznaczonej prostej do tego punktu we wszystkich kierunkach.

Jaki jest problem?

Problem jest z prędkością poruszania się komponentu. Im prosta jest bardziej stroma, tym komponent porusza się szybciej. Najczęściej mniejsza wartość dx oraz większa dy, więc komponent ma dłuższą drogę w pionie niż w poziomie czyli wyliczone wartości y są większe i to powoduje większą prędkość. (większe przeskoki)

Bardzo proszę o cenne wskazówki.

1

Skorzystaj z równania parametrycznego prostej.

1

Mi się wydaję, że jak tak robisz, to nie masz jednakowej wartości prędkości w każdym kierunku, tylko w każdym inna jest ta wartość i przez to inaczej leci w dół, a inaczej na skos. A jak by tu za pomocą funkcji trygonometrycznych zrobić to? Wtedy, można policzyć składowe dla jednakowego Z czyli tej prędkości głównej w każdym kierunku.

1

Bo przyjąłeś stały krok na osi X (zawsze 1 piksel), a krok na osi Y dostosowujesz do kroku na osi X stąd stała prędkość pozioma i dostosowana do niej prędkość pionowa. Dodatkowo pozycję na osi Y kumulujesz w zmiennej całkowitej - źle gdyż z każdym krokiem ucinana jest część ułamkowa i błąd jest co raz większy.

Lepiej będzie zrobić to inaczej. Są w zasadzie 2 sensowne możliwości.

Możliwość 1. Dzielimy ruch na ustaloną ilość kroków (zawsze identyczny czas ruchu):

Left := x1 + I * (x2 - x1) div N;
Top := y1 + I * (y2 - y1) div N;

gdzie:
N - ilość kroków
I - aktualny krok

Możliwość 2. Dzielimy ruch na kroki o ustalonej długości (zawsze identyczna prędkość ruchu):

Left := x1 + I * V * (x2 - x1) / D;
Top := y1 + I * V * (y2 - y1) / D;

gdzie:
I - aktualny krok
V - prędkość (piksele/krok)
D = Sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))

1

A czy takie rozwiązanie jest prawidłowe, czy wyjdą ze tego zmutowane owce?

ss.png

Najpierw obliczam składowe x i y, potem z tego kąt alfa. Potem jak już mam alfa, to wykorzystuję sin i cos, aby obliczyć nowe składowe dla Z = 1.

0

Dziękuję bardzo za zainteresowanie tematem ( nie sądziłem, że będzie takie duże, a tu proszę :D)

Na chwilę obecną rozwiązałem problem metodą xeo545x39. Nie sądziłem, że będzie to takie proste, a ja kombinowałem zupełnie niepotrzebnie :)
Skoro twierdzicie, że postać parametryczna będzie lepsza, to spróbuje ją jeszcze zaimplementować i porównam.

0

Jak już poprawnie wyznaczasz długość skoku (czy to z pitagorasa, czy przy użyciu trygonometrii, czy po prostu dzieląc drogę na N równych kroków) to drugą sprawą o jaką musisz zadbać to poprawne kumulowanie wyniku. Nie rób tak:

Left := Left + SkokX;
Top := Top + SkokY;

bo z każdym takim dodawaniem ucinasz część ułamkową skoku i wyniki stają się co raz bardziej błędne.
Użyj dodatkowej zmiennej typu zmiennoprzecinkowego (np. Float) do sumowania skoków (byle nie zmiennej lokalnej, wartości muszą być pamiętane pomiędzy kolejnymi tyknięciami):

PozycjaX := PozycjaX + SkokX;
Left := PozycjaX;
PozycjaY := PozycjaY + SkokY;
Top := PozycjaY;

lub wyznaczaj pozycję na podstawie licznika kroków (możesz użyć do tego osobnej zmiennej lub własności Tag Timer'a):

Left := PoczatekX + SkokX * Timer1.Tag;
Top := PoczatekY + SkokY * Timer1.Tag;
Inc(Timer1.Tag);
1
  TForm1=class(TForm)
    Panel1:TPanel;
    Timer:TTimer;
    procedure FormMouseDown(Sender:TObject;Button:TMouseButton;Shift:TShiftState;X,Y:Integer);
    procedure TimerTimer(Sender:TObject);
  private
    Sx,Sy:Integer;
  public
  end; 

var Form1: TForm1;
const Speed=20;

implementation uses
  Math;

{$R *.lfm}

{ TForm1 }

procedure TForm1.FormMouseDown(Sender:TObject;Button:TMouseButton;Shift:TShiftState;X,Y:Integer);
begin
  Sx:=X-((Panel1.Width)shr(1));
  Sy:=Y-((Panel1.Height)shr(1));
end;

procedure TForm1.TimerTimer(Sender:TObject);
var Dx,Dy,Len:Extended;
begin
  if (Panel1.Left<>Sx)or(Panel1.Top<>Sy) then
  begin
    Dx:=Sx-Panel1.Left;
    Dy:=Sy-Panel1.Top;
//      Len:=Sqrt(Dx*Dx+Dy*Dy);
//      Dx:=Min(1,Speed/Len)*Dx;
//      Dy:=Min(1,Speed/Len)*Dy;
//      Panel1.BoundsRect:=Bounds(Round(Panel1.Left+Dx),Round(Panel1.Top+Dy),Panel1.Width,Panel1.Height);
    Len:=Min(1,Speed/Sqrt(Dx*Dx+Dy*Dy));
    Panel1.BoundsRect:=Bounds(Round(Panel1.Left+Len*Dx),Round(Panel1.Top+Len*Dy),Panel1.Width,Panel1.Height);
  end;
end;
0

No fajnie. Ale nasz Panel nie porusza się po linii prostej.

Clipboard01.jpg

ps
Jak się wstawia obrazki bez pośrednictwa obcych serwerów?

0

Bardzo dziękuję wszystkim za odpowiedzi.

Zdecydowałem się na rozwiązanie zapodane przez użytkownika _13th_Dragon, gdyż wydaję się być najprostszym a jednocześnie zadowala mnie wystarczająco.
Problem uważam za rozwiązany, jednakże gdy ktoś chce pobić rekord ustanowiony przez tego użytkownika, lub jeszcze trochę to poprawić to proszę bardzo :D

Jeszcze raz dziękuję za sugestie i pozdrawiam :)

2
TForm1=class(TForm)
    Panel1:TPanel;
    Timer:TTimer;
    procedure FormMouseDown(Sender:TObject;Button:TMouseButton;Shift:TShiftState;X,Y:Integer);
    procedure TimerTimer(Sender:TObject);
  private
    Sx,Sy:Integer;
    Px,Py:Extended;
  public
  end; 
 
var Form1: TForm1;
const Speed=20;
 
implementation uses
  Math;
 
{$R *.lfm}
 
{ TForm1 }
 
procedure TForm1.FormMouseDown(Sender:TObject;Button:TMouseButton;Shift:TShiftState;X,Y:Integer);
begin
  Sx:=X-((Panel1.Width)shr(1));
  Sy:=Y-((Panel1.Height)shr(1));
  Px:=Panel1.Left;
  Py:=Panel1.Top;
end;
 
procedure TForm1.TimerTimer(Sender:TObject);
var X,Y,Q:Extended;
begin
  if (Panel1.Left<>Sx)or(Panel1.Top<>Sy) then
  begin
    X:=Sx-Px;
    Y:=Sy-Py;
    Q:=Min(1,Speed/Sqrt(X*X+Y*Y));
    Px:=Px+Q*X;
    Py:=Py+Q*Y;
    Panel1.BoundsRect:=Bounds(Round(Px),Round(Py),Panel1.Width,Panel1.Height);
  end;
end;

To będzie idealnie równe (no chyba że ekran będzie wielkości dużego budynku przy 2000 dpi), ale też trochę więcej kodu i trochę więcej danych w klasie.

0

Witajcie ponownie :)

Wszystko działa znakomicie, jestem Wam bardzo wdzięczny za pomoc, ale pojawił się pewien problem, który z pozoru wydawał mi się banalny, jednakże taki nie jest (przynajmniej dla mnie).

O co chodzi? Już wyjaśniam :)

Załóżmy, że mamy tak poruszający się komponent umieszczony na TScrollBox'ie. Scrolle w ScrollBoxie są wycentrowane w ten sposób, że dany komponent znajduje się idealnie na środku ScrollBoxa. Już pewnie domyślacie się, że chodzi mi o płynne automatyczne przesuwanie scrolli, gdy komponent się porusza (tak, aby zawsze był wycentrowany).

No dobrze, wycentrować scrolle umiem, np tak:

Scroll.HorzScrollBar.Position := Scroll.HorzScrollBar.Position + Komponent.Left + (Komponent.Width - ClientWidth) div 2;
Scroll.VertScrollBar.Position := Scroll.VertScrollBar.Position + Komponent.Top + (Komponent.Height - ClientHeight) div 2;

Ładnie się centrują, ale gdy komponent nie jest w ruchu, gdy się porusza wszystko zaczyna wariować.
Problem tkwi w tym, że scrollbox nie pokazuje globalnych pozycji komponentów, tylko ze względu na przemieszczenie suwaków. (Po prostu zmienia Top,Left komponentów tak, aby były widoczne), więc kiedy komponent się porusza po lini prostej i próbuje wtedy jeszcze zmienić pozycję suwaków, wszystko ogarnia chaos.
Lekarstwem na mą bolączkę jest na pewno dodanie/odjęcie gdzieniegdzie pozycji scrolli, tak aby wszystko ładnie ze sobą współgrało, jednak nie mogę sobie z tym poradzić, bo za każdym razem coś ze sobą koliduje.

Z góry dziękuję za wszelkie pomysły, propozycje...

0

Wybaczcie, że piszę post pod postem, ale chciałbym zostać zauważony.

W załączniku przesyłam projekt. Mi już głowa od tego pęka, może ktoś bardziej rozgarnięty mi pomoże.

Pozdrawiam :)

edit:

Problem został rozwiązany. Panel którym steruje umieściłem na innym panelu, który odpowiednio przesuwam z panelem 'chodzącym'. Nie ma już kolizji z wartościami Top, Left :) Pozdrawiam i dziękuję :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1