Jak przesunąć obiekt wzdłuż obwodu innego obiektu?

11

Jestem tak matematyczny, że boli, ale dla niektórych z was to powinno być bułka z masłem. Chcę przesuwać obiekt wokół drugiego wzdłuż jego wieku lub obwodu po prostej, okrągłej ścieżce. W tej chwili mój algorytm gry wie, jak poruszać się i ustawiać duszka tuż przy krawędzi przeszkody, a teraz czeka na ruch następnego punktu w zależności od różnych warunków.

Problem matematyczny polega więc na tym, jak uzyskać pozycje (aX, aY) i (bX, bY) , gdy znam środek (cX, cY), pozycję obiektu (oX, oY) i odległość wymaganą do przesunięcia (d)

wprowadź opis zdjęcia tutaj

Lumis
źródło
1
Czy dodległość jest liniowa czy jest to łuk?
MichaelHouse
Jest to odległość liniowa w pikselach
Lumis
Czy w ogóle wiesz, czym są wektory i jakie są podstawowe operacje na nich?
Patrick Hughes
@Patrick Nie, chyba będę musiał zrobić kurs na temat wektorów. Ponieważ jest to animacja klatka po klatce, kod powinien być szybki i zoptymalizowany.
Lumis

Odpowiedzi:

8

( CAVEAT: Używam tutaj dwóch aproksymacji: pierwsza przyjmuje d jako długość łuku, a druga przyjmuje ją jako długość ortogonalną. Oba te aproksymacje powinny być dobre dla stosunkowo małych wartości d, ale nie spełniają dokładne pytanie wyjaśnione w komentarzach).

Na szczęście matematyka na ten temat jest stosunkowo prosta. Przede wszystkim możemy znaleźć wektor względny od naszej pozycji środkowej do naszej aktualnej pozycji:

deltaX = oX-cX;
deltaY = oY-cY;

Po uzyskaniu tego wektora względnego możemy poznać promień okręgu, nad którym pracujemy, znajdując jego długość:

radius = sqrt(deltaX*deltaX+deltaY*deltaY);

Co więcej, z naszego wektora względnego możemy znaleźć dokładny kąt, w którym linia od cX do oX ma:

curTheta = atan2(deltaX, deltaY);

Teraz sprawy stają się trochę trudniejsze. Przede wszystkim zrozum, że obwód koła - to znaczy „długość łuku” łuku o miary kątowej 2π - wynosi 2πr. Zasadniczo długość łuku łuku o wymiarze kątowym θ wzdłuż okręgu o promieniu r wynosi tylko θr. Gdybyśmy użyli litery d na twoim diagramie jako długości łuku, a ponieważ znamy promień, możemy znaleźć zmianę w theta, aby doprowadzić nas do nowej pozycji, po prostu dzieląc:

deltaTheta = d/radius; // treats d as a distance along the arc

W przypadku, gdy d musi być odległością liniową, rzeczy są nieco bardziej skomplikowane, ale na szczęście niewiele. Tam, d jest jedną stroną trójkąta izoceles, którego pozostałe dwa boki są promieniem koła (odpowiednio od cX / cY do oX / oY i aX / aY), a podzielenie tego trójkąta izocelowego daje nam dwa trójkąty prawe, z których każdy ma d / 2 jako jedną stronę i promień jako przeciwprostokątną; oznacza to, że sinus połowy naszego kąta wynosi (d / 2) / promień, a zatem pełny kąt jest tylko dwa razy większy:

deltaTheta = 2*asin(d/(2*radius)); // treats d as a linear distance

Zauważ, że jeśli wziąłeś asin z tej formuły i anulowałeś 2s, byłoby to takie samo jak w ostatnim wzorze; jest to to samo, co stwierdzenie, że sin (x) wynosi w przybliżeniu x dla małych wartości x, co jest przydatnym przybliżeniem do poznania.

Teraz możemy znaleźć nowy kąt, po prostu dodając lub odejmując:

newTheta = curTheta+deltaTheta; // This will take you to aX, aY. For bX/bY, use curTheta-deltaTheta

Po uzyskaniu nowego kąta możemy użyć podstawowego wyzwalacza, aby znaleźć nasz zaktualizowany wektor względny:

newDeltaX = radius*cos(newTheta);
newDeltaY = radius*sin(newTheta);

a z naszej pozycji środkowej i naszego wektora względnego możemy (w końcu) znaleźć punkt docelowy:

aX = cX+newDeltaX;
aY = cY+newDeltaY;

Teraz, mając to wszystko na uwadze, należy pamiętać o kilku dużych zastrzeżeniach. Po pierwsze, zauważysz, że matematyka jest w większości zmiennoprzecinkowa i faktycznie musi być; próba użycia tej metody do aktualizacji w pętli i zaokrąglania z powrotem do wartości całkowitych na każdym kroku może zrobić wszystko, od sprawienia, że ​​koło się nie zamyka (spirala do wewnątrz lub na zewnątrz za każdym razem, gdy okrążasz pętlę), po to, aby nie zacząć od pierwszego miejsce! (Jeśli twoje d jest zbyt małe, możesz odkryć, że zaokrąglone wersje aX / aY lub bX / bY są dokładnie tam, gdzie była twoja pozycja początkowa oX / oY.) Po drugie, jest to bardzo drogie, szczególnie za to, co próbuje zrobić; ogólnie, jeśli wiesz, że twoja postać będzie poruszać się po łuku kołowym, powinieneś zaplanować cały łuk z wyprzedzeniem, a niezaznaczaj go od ramki do ramki w ten sposób, ponieważ wiele z najdroższych obliczeń tutaj można załadować z przodu, aby obniżyć koszty. Innym dobrym sposobem na obniżenie kosztów, jeśli naprawdę chcesz aktualizować stopniowo w ten sposób, jest nie używanie triggera; jeśli d jest małe i nie potrzebujesz go dokładnie, ale po prostu bardzo blisko, możesz zrobić „lewę”, dodając wektor długości d do oX / oY, prostopadły do ​​wektora w kierunku środka (zwróć uwagę, że a wektor ortogonalny do (dX, dY) jest podawany przez (-dY, dX)), a następnie zmniejszony do odpowiedniej długości. Nie będę wyjaśniać tego kodu krok po kroku, ale mam nadzieję, że będzie miał sens, biorąc pod uwagę to, co do tej pory widziałeś. Zauważ, że „zmniejszamy” nowy wektor delta domyślnie w ostatnim kroku,

deltaX = oX-cX; deltaY = oY-cY;
radius = sqrt(deltaX*deltaX+deltaY*deltaY);
orthoX = -deltaY*d/radius;
orthoY = deltaX*d/radius;
newDeltaX = deltaX+orthoX; newDeltaY = deltaY+orthoY;
newLength = sqrt(newDeltaX*newDeltaX+newDeltaY*newDeltaY);
aX = cX+newDeltaX*radius/newLength; aY = cY+newDeltaY*radius/newLength;
Steven Stadnicki
źródło
1
Steven Myślę, że najpierw spróbuję dokonać przybliżenia, ponieważ jest to tylko gra, w której ważniejsze jest, aby poczuć się naturalnie i interesująco niż precyzyjnie. Liczy się także prędkość. Dziękuję za ten długi i dobry samouczek!
Lumis
Wow, Steven, twoje przybliżenie działa jak sen! Czy możesz mi powiedzieć, jak zmienić kod, aby uzyskać bX, bY. Nie jestem jeszcze pewien twojej ortogonalnej koncepcji ...
Lumis
2
Pewnie! W pewnym momencie naprawdę będziesz chciał zrozumieć matematykę wektorową, a kiedy to zrobisz, podejrzewam, że będzie to druga natura; aby uzyskać bX / bY, wystarczy przejść „na odwrót” - innymi słowy, zamiast dodawać (konkretny) ortogonalny wektor, wystarczy go odjąć. W odniesieniu do powyższego kodu będzie to „newDeltaX = deltaX-orthoX; newDeltaY = delta-orto; ”, po którym następuje takie samo obliczenie newLength, a następnie„ bX = cX + nowy promień DeltaX / newLength; bY = cY + nowy promień DeltaY / newLength; ”.
Steven Stadnicki
Zasadniczo kod ten wskazywałby newDeltaX / newDeltaY w kierunku bX / bY (zamiast w kierunku aX / aY), a następnie przycinał, aby dopasować i dodać do środka, tak jak byś otrzymał aX / aY.
Steven Stadnicki
9

Utwórz trójkąt, korzystając z dwóch boków, które już masz (jeden bok to „c” na „o”, drugi to „o” na „a”), a trzeci bok to „a” na „c”. Nie wiesz jeszcze, gdzie jest „a”, po prostu wyobraź sobie, że jest na razie sens. Będziesz potrzebować trygonometrii, aby obliczyć kąt kąta przeciwny do boku „d”. Masz długość boków c <-> o ic <-> a, ponieważ oba mają promień okręgu.

Teraz, gdy masz już długość trzech boków tego trójkąta, którego jeszcze nie widzisz, możesz określić kąt, który jest przeciwny do boku „d” trójkąta. Oto formuła SSS (side-side-side), jeśli potrzebujesz: http://www.teacherschoice.com.au/maths_library/trigonometry/solve_trig_sss.htm

Korzystając ze wzoru SSS masz kąt (który nazwiemy „j”), który jest przeciwny do boku „d”. Teraz możemy obliczyć (aX, aY).

// This is the angle from 'c' to 'o'
float angle = Math.atan2(oY - cY, oX - cX)

// Add the angle we calculated earlier.
angle += j;

Vector2 a = new Vector2( radius * Math.cos(angle), radius * Math.sin(angle) );

Upewnij się, że kąty, które obliczasz, zawsze są w radianach.

Jeśli musisz obliczyć promień okręgu, możesz użyć odejmowania wektora, odjąć punkt „c” od punktu „o”, a następnie uzyskać długość wynikowego wektora.

float lengthSquared = ( inVect.x * inVect.x
                      + inVect.y * inVect.y
                      + inVect.z * inVect.z );

float radius = Math.sqrt(lengthSquared);

Uważam, że coś takiego powinno zrobić. Nie znam Java, więc zgadłem, jaka jest dokładna składnia.

Oto obraz podany przez użytkownika w Byte56celu zilustrowania wyglądu tego trójkąta: trójkąt cao

Nic Foster
źródło
1
Odpowiedziałem, ale o to chodzi. Możesz użyć obrazu, który zrobiłem :) i.imgur.com/UUBgM.png
MichaelHouse
@ Byte56: Dzięki, nie miałem żadnego uchwytu edytora obrazów do zilustrowania.
Nic Foster
Zauważ, że promień również musi zostać obliczony; powinny istnieć prostsze sposoby uzyskania j niż pełne obliczenia SSS, ponieważ mamy trójkąt izoceles.)
Steven Stadnicki
Tak, nawet dla mnie wydaje się to proste! Android nie ma Vector2, więc chyba mogę użyć tych wartości osobno. Co ciekawe, znalazłem klasę Vector2 utworzoną ręcznie dla Androida tutaj: code.google.com/p/beginning-android-games-2/source/browse/trunk/…
Lumis
(Poprawiłem własną odpowiedź, aby znaleźć prawidłową odległość liniową - drugie obliczenie delta Theta tam, jako 2 * asin (promień d / (2 *)), jest jak znaleźć tutaj j.)
Steven Stadnicki
3

Aby obrócić obiekt obj2 wokół obiektu obj1, spróbuj:

float angle = 0; //init angle

//call in an update
obj2.x = (obj1.x -= r*cos(angle));
obj2.y = (obj1.y += r*sin(angle));
angle-=0.5;
Chwytak
źródło
To nie pokazuje, jak uzyskać kąt, a wydaje się, że pokazujesz, jak orbitować, zamiast znajdować współrzędne, jak pyta pytanie.
MichaelHouse
1
Lewis, dziękuję za pokazanie, jak okrążyć obiekt wokół punktu. Może się przydać ...
Lumis