Pierwsze sugestie dotyczące wybranej metody:
Co nazywasz ac
faktycznie cb
. Ale jest ok, to jest to, co naprawdę potrzebne. Dalej,
float dotabac = (ab.x * ab.y + ac.x * ac.y);
To jest twój pierwszy błąd. realny produkt skalarny dwóch wektorów jest:
float dotabac = (ab.x * ac.x + ab.y * ac.y);
Teraz
float rslt = acos(dacos);
Tutaj należy zauważyć, że z powodu jakiegoś precyzyjnego straty podczas obliczeń jest to teoretycznie możliwe, że dacos
będzie większy niż 1 (lub mniejszy niż -1). Dlatego należy to wyraźnie sprawdzić.
Plus uwaga o wydajności: wywołujecie ciężką funkcję sqrt
dwukrotnie do obliczenia długości dwóch wektorów. Następnie dzielisz produkt kropkowany przez te długości. Zamiast tego można nazwać sqrt
na mnożenie kwadratów o długości obu wektorów.
Na koniec należy zauważyć, że wynik jest dokładny aż do sign
. Oznacza to, że twoja metoda nie rozróżnia 20 ° i -20 °, ponieważ cosinus obu jest taki sam. Twoja metoda daje ten sam kąt dla ABC i CBA.
jedna poprawna metoda obliczania kąta jest jako "oslvbo" proponuje:
float angba = atan2(ab.y, ab.x);
float angbc = atan2(cb.y, cb.x);
float rslt = angba - angbc;
float rs = (rslt * 180)/3.141592;
(Właśnie zastąpiony atan
przez atan2
).
To najprostsza metoda, która zawsze daje prawidłowy wynik. Wadą tej metody jest dwukrotne wywołanie ciężkiej funkcji trygonometrii atan2
.
Proponuję następującą metodę. Jest nieco bardziej złożony (wymaga zrozumienia pewnych umiejętności trygonometrycznych), jednak jest lepszy od punktu widzenia wydajności. Po prostu wywołuje funkcję trygonometrii atan2
. I bez obliczeń pierwiastków kwadratowych.
int CGlEngineFunctions::GetAngleABC(POINTFLOAT a, POINTFLOAT b, POINTFLOAT c)
{
POINTFLOAT ab = { b.x - a.x, b.y - a.y };
POINTFLOAT cb = { b.x - c.x, b.y - c.y };
// dot product
float dot = (ab.x * cb.x + ab.y * cb.y);
// length square of both vectors
float abSqr = ab.x * ab.x + ab.y * ab.y;
float cbSqr = cb.x * cb.x + cb.y * cb.y;
// square of cosine of the needed angle
float cosSqr = dot * dot/abSqr/cbSqr;
// this is a known trigonometric equality:
// cos(alpha * 2) = [ cos(alpha) ]^2 * 2 - 1
float cos2 = 2 * cosSqr - 1;
// Here's the only invocation of the heavy function.
// It's a good idea to check explicitly if cos2 is within [-1 .. 1] range
const float pi = 3.141592f;
float alpha2 =
(cos2 <= -1) ? pi :
(cos2 >= 1) ? 0 :
acosf(cos2);
float rslt = alpha2/2;
float rs = rslt * 180./pi;
// Now revolve the ambiguities.
// 1. If dot product of two vectors is negative - the angle is definitely
// above 90 degrees. Still we have no information regarding the sign of the angle.
// NOTE: This ambiguity is the consequence of our method: calculating the cosine
// of the double angle. This allows us to get rid of calling sqrt.
if (dot < 0)
rs = 180 - rs;
// 2. Determine the sign. For this we'll use the Determinant of two vectors.
float det = (ab.x * cb.y - ab.y * cb.y);
if (det < 0)
rs = -rs;
return (int) floor(rs + 0.5);
}
EDIT:
Ostatnio pracuję na pokrewny temat. I wtedy zrozumiałem, że jest lepszy sposób. W rzeczywistości jest mniej więcej tak samo (za kulisami). Jednak jest to bardziej proste IMHO.
Chodzi o to, aby obrócić oba wektory tak, aby pierwszy był ustawiony na (dodatni) kierunek X. Oczywiście obracanie obu wektorów nie wpływa na kąt między nimi. OTOH po takim obrocie musi właśnie znaleźć kąt drugiego wektora względem osi X. Właśnie do tego służy atan2
.
Obrót uzyskuje się poprzez pomnożenie wektora przez następującą macierz:
Po może zobaczyć, że wektor a
pomnożonej przez takiej macierzy rzeczywiście obraca się w kierunku dodatniej osi X.
Uwaga: Ściśle mówiąc powyższa macierz nie tylko się obraca, ale także skaluje. Ale w naszym przypadku jest to ok, ponieważ jedyną rzeczą, która ma znaczenie, jest kierunek wektora, a nie jego długość.
Obrócona wektor b
postać:
- a.x * B.x + a.y * b.y = kropka b
- -a.y * B.x + a.x * b *.Y = przekroju b
Wreszcie, że odpowiedź może być wyrażona jako:
int CGlEngineFunctions::GetAngleABC(POINTFLOAT a, POINTFLOAT b, POINTFLOAT c)
{
POINTFLOAT ab = { b.x - a.x, b.y - a.y };
POINTFLOAT cb = { b.x - c.x, b.y - c.y };
float dot = (ab.x * cb.x + ab.y * cb.y); // dot product
float cross = (ab.x * cb.y - ab.y * cb.x); // cross product
float alpha = atan2(cross, dot);
return (int) floor(alpha * 180./pi + 0.5);
}
robię całkiem dobrze, masz algorthm dla niego, ale to nie robi trick. – jmasterx
@abelenky: a to sprawia, że pytanie jest "niejasne lub nieprzydatne" jak dokładnie? Być może źle zrozumiałeś cel rep. To nie jest tam, aby pozwolić ci karać ludzi za próbowanie zrobienia czegoś, co jest dla nich nowe. – jalf