• Nie Znaleziono Wyników

WYRÓWNAJ DO WEKTORA

W dokumencie Ruch. Podstawy algorytmów ruchu (Stron 135-142)

Zarówno zachowanie kierowania twarzą, jak i wygląd miejsca, w którym się poruszasz, rozpoczęły się od wektora, wzdłuż którego znak powinien się ustawić. W pierwszym przypadku jest to wektor z aktualnej pozycji znaku do celu, w drugim przypadku jest to wektor prędkości. Zakładamy, że postać próbuje ustawić swoją oś Z (oś, na którą patrzy) w podanym kierunku. W dwóch wymiarach łatwo jest

obliczyć orientację celu z wektora za pomocą funkcji atan 2 dostępnej w większości języków. W trzech wymiarach nie ma takiego skrótu do generowania kwaternionu z wektora skierowanego do celu. W rzeczywistości istnieje nieskończona liczba orientacji, które spoglądają w dół na dany wektor, jak pokazano na rysunku

Wektor kropkowany jest rzutem wektora bryłowego na płaszczyznę x– z: cień, który daje wizualną wskazówkę. Szare wektory reprezentują trzy osie. Oznacza to, że nie ma jednego sposobu na przekonwertowanie wektora na orientację. Aby uprościć sprawę, musimy przyjąć pewne założenia.

Najczęstszym założeniem jest skierowanie celu w kierunku orientacji „bazowej”. Chcielibyśmy wybrać orientację, która jest jak najbliższa orientacji podstawowej. Innymi słowy, zaczynamy od orientacji bazowej i obracamy ją o możliwie najmniejszy kąt (wokół odpowiedniej osi), tak aby jej lokalna oś z wskazywała wzdłuż naszego wektora docelowego. Ten minimalny obrót można znaleźć, przekształcając kierunek z orientacji podstawy na wektor, a następnie biorąc iloczyn wektorowy tego i wektora docelowego. Iloczyn wektorowy daje:

gdzie,

jest wektorem lokalnego kierunku z w orientacji bazowej , jest wektorem docelowym, a będzie produktem krzyżowym które jest definiowane jako

gdzie 9 to kąt, a to oś minimalnego obrotu. Ponieważ oś będzie wektorem jednostkowym (tj.

)możemy wyznaczyć kąt i podzielić f przez to, aby otrzymać oś. To nie będzie działać, jeśli sin 9 = 0 (tj. 9 = nn dla wszystkich n ∈ Z). Odpowiada to naszej intuicji na temat fizycznych właściwości obrotu. Jeśli kąt obrotu jest 0, to nie ma sensu mówić o żadnej osi obrotu. Jeśli obrót wynosi n radianów (90 °), wystarczy dowolna oś; nie ma określonej osi wymagającej mniejszego obrotu niż jakakolwiek inna. Tak długo jako sin 0 Λ 0 możemy wygenerować orientację celu,

najpierw obracając oś i kąt w kwaternion, r i stosując wzór:

gdzie jest reprezentacją kwaternionów orientacji podstawy, a jest orientacją docelową, do której ma zostać wyrównana. Jeśli sin # = 0, to mamy dwie możliwe sytuacje: albo docelowa oś z jest

taka sama jak podstawowa oś z, albo jest od niej w radianach π. Innymi słowy, . W każdym przypadku używamy kwaternionu orientacji bazowej, z odpowiednią zmianą znaku:

Najbardziej powszechną orientacją podstawową jest orientacja zerowa: [1 0 0 0]. Powoduje to, że postać pozostaje wyprostowana, gdy jej cel znajduje się w płaszczyźnie x-z. Poprawienie wektora podstawowego może dać przyjemne wizualnie efekty. Moglibyśmy na przykład przechylić orientację podstawową, gdy rotacja postaci jest duża, aby zmusić ją do przechylania się w jej obrotach.

Zaimplementujemy ten proces w kontekście zachowania sterowania twarzą poniżej.

TWARZ

Używając procesu wyrównywania do wektora, zarówno twarz, jak i wygląd tam, gdzie się wybierasz, można łatwo zaimplementować przy użyciu tego samego algorytmu, którego używaliśmy na początku rozdziału, zastępując obliczenie tan 2 powyższą procedurą obliczania nowego celu orientacja. Tytułem ilustracji podamy implementację zachowania kierowania twarzą w trzech wymiarach. Ponieważ jest to modyfikacja algorytmu podanego wcześniej w tym rozdziale, nie będziemy omawiać go szczegółowo class Face3D ( A1 ign3D) :

# The base orientation used to calculate facing baseOrientation

# Overridden target target

# ... Other data is derived from the superclass ...

# Calculate an orientation for a given vector def cal cul ateOrientation 12 (vector) :

# Get the base vector by transforming the z-axis by base

# orientation ( this only needs to be done once for each base

# orientation , so could be cached between calls ) . baseZVector = new Vector (0 , 0,1) * baseOrientation

# If the base vector is the same as the target , return

# the base quaternion if baseZVector == vector:

return baseOrientation

# If i t is the exact opposite , return the inverse of the base

# quaternion

if baseZVector == -vector:

return - baseOrientation

# Otherwise find the minimum rotation from the base to the target change = baseZVector x vector

# Find the angle and axis

angle = arcsin ( change.length ( ) ) axis = change

axis .normalize ( )

# Pack these into a quaternion and return i t return new Quaternion ( cos ( angle/2 ) , sin (angle/2)*axis.x ,

sin ( angle/2 )*axis.y , sin (angle/2)*axis.z )

# Implemented as i t was in Pursue def getSteeringO:

# 1. Calculate the target 47 to delegate to align

# Work out the direction to target direction = target .position - character.position

# Check for a zero direction , and make no change if so if direction.length ( ) = = 0: return target

# Put the target together A1 ign3D .target = explicitTarget

A1 ign3D .target .orientation = calculateOrientation (directi on )

# 2. Delegate to align

return A1 ign3D .getSteeringO

Ta implementacja zakłada, że możemy wziąć iloczyn wektorowy dwóch wektorów za pomocą składni vectorl x vector2. Operator x nie istnieje w większości języków. Na przykład w C ++ można w tym celu użyć wywołania funkcji lub przeciążenia operatora dzielenia modularnego%. Musimy również przyjrzeć się mechanice przekształcania wektora przez kwaternion. W powyższym kodzie jest to wykonywane za pomocą operatora *, więc wektor * kwaternion powinien zwrócić wektor, który jest równoważny z obróceniem danego wektora o kwaternion. Matematycznie jest to podane przez:

gdzie , jest kwaternionem wyprowadzonym z wektora, zgodnie z

a jest koniugatem kwaternionu, który jest taki sam jak odwrotność dla kwaternionów jednostkowych. Można to zaimplementować jako:

# Transforms the vector by the given quaternion def transform( vector, orientation ) :

# Convert the vector into a quaternion

vectorAsQuat = Quaternion ( 0 , vector.x , vector.y, vector.z )

# Transform i t

vectorAsQuat = orientation * vectorAsQuat * (-orientation )

# Unpick i t into the resulting vector

return new Vector ( vectorAsQuat .i , vectorAsQuat . j , vectorAsQuat .k) Mnożenie kwaternionów z kolei jest definiowane przez:

Należy pamiętać, że kolejność ma znaczenie. W przeciwieństwie do zwykłej arytmetyki, mnożenie kwaternionów nie jest przemienne. Ogólnie

Zobacz dokąd zmierzasz

Spójrz, gdzie zmierzasz, miałbyś bardzo podobną implementację. Po prostu zastępujemy obliczenie wektora kierunku w metodzie getSteeri ng obliczeniem opartym na aktualnej prędkości postaci:

# Wypracuj kierunek celu direction = character.velocity directi on .normal ize ()

WĘDRÓWKA

W wędrówce 2D, punkt docelowy był zmuszony do poruszania się po okręgu przesuniętym przed postacią w pewnej odległości. Cel przemieszczał się losowo wokół tego koła. Pozycja celu była utrzymywana pod kątem, reprezentującym, jak daleko wokół okręgu leżał cel, a losowa zmiana w tym była generowana przez dodanie losowej wartości do kąta. W trzech wymiarach równoważne zachowanie wykorzystuje sferę 3D, na której utwierdzony jest cel, ponownie odsuniętą w pewną odległość przed postacią. Nie możemy jednak użyć jednego kąta do przedstawienia położenia celu na kuli. Moglibyśmy użyć kwaternionu, ale trudno jest go zmienić o niewielką, losową wartość bez dużej ilości matematyki. Zamiast tego reprezentujemy położenie celu na kuli jako wektor 3D, ograniczający wektor ma długość jednostkową. Aby zaktualizować jego położenie, po prostu dodajemy losową ilość do każdego składnika wektora i ponownie normalizujemy. Aby uniknąć przypadkowej zmiany powodującej wektor 0 (a tym samym uniemożliwiającej normalizację), upewniamy się, że maksymalna zmiana w dowolnej składowej jest mniejsza niż 1 / V3. Po zaktualizowaniu docelowej pozycji na kuli, przekształcamy ją przez orientację postaci, skalujemy ją o promień wędrówki, a następnie przesuwamy przed postać o przesunięcie wędrówki, dokładnie tak jak w przypadku 2D. To utrzymuje cel przed postacią i zapewnia niskie kąty skrętu. Zamiast używać pojedynczej wartości dla przesunięcia wędrówki, używamy teraz wektora. To pozwoliłoby nam zlokalizować krąg wędrówki w dowolnym miejscu względem postaci. Nie jest to szczególnie przydatna funkcja, chcemy, aby znajdowała się przed znakiem (tzn. Miała tylko dodatnią współrzędną z, z 0 dla wartości xiy). Jednak posiadanie go w postaci wektorowej upraszcza matematykę. To samo dotyczy właściwości maksymalnego przyspieszenia:

zastąpienie wartości skalarnej wektorem 3D upraszcza obliczenia i zapewnia większą elastyczność.

Mając docelową lokalizację w przestrzeni świata, możemy użyć zachowania twarzy 3D, aby obrócić się w jej kierunku i przyspieszyć do przodu w największym możliwym stopniu. W wielu grach 3D chcemy zachować wrażenie, że istnieje kierunek w górę iw dół. Ta iluzja jest uszkodzona, jeśli wędrowiec może zmieniać kierunek w górę iw dół tak szybko, jak to możliwe w płaszczyźnie x– z. Aby to wesprzeć, możemy użyć dwóch promieni do skalowania pozycji docelowej: jednego do skalowania składowych x i z, a drugiego do skalowania składowej y. Jeśli skala y jest mniejsza, to wędrowiec skręci szybciej w płaszczyźnie x– z. W połączeniu z wykorzystaniem opisanej powyżej implementacji twarzy, z orientacją podstawową, gdzie góra jest skierowana w stronę osi y, daje to naturalny wygląd latającym postaciom, takim jak pszczoły, ptaki lub samoloty. Nowe zachowanie wędrówki można zaimplementować w następujący sposób:

class i Wander3D ( Face3D) :

# Holds the radius and offset of the wander circle. The

# offset is now a full 3D vector.

wanderOffset wanderRadiusXZ wanderRadiusY

# Holds the maximum rate at which the wander orientation

# can change. Should be st r ict ly less than

# 1/sqrt (3) = 0.577 to avoid the chance of ending up with

# a zero length wanderVector.

wanderRate

# Holds the current offset of the wander target wanderVector

# Holds the maximum acceleration of the character , this

# again should be a 3D vector , typically with only a

# non-zero z value.

maxAcceleration

# ... Other data is derived from the superclass ...

def getSteeringO :

# 1. Calculate the target to delegate to face

# Update the wander direction

wanderVector.x += randomBinomial ( ) * wanderRate wanderVector.y += randomBinomial ( ) * wanderRate wanderVector.z += randomBinomial ( ) * wanderRate wanderVector.normalize ( )

# Calculate the transformed target direction and scale i t target = wanderVector * character.orientation

target .x *= wanderRadiusXZ target .y *= wanderRadiusY target .z *= wanderRadiusXZ

# Offset by the center of the wander circle target += character .position +

wanderOffset * character .orientation

# 2. Delegate i t to face

steering = Face3D.getSteering ( target )

# 3. Now set the linear acceleration to be at full

# acceleration in the direction of the orientation

steering.1 inear = maxAcceleration * character.orientation

# Return i t return steering

Ponownie, jest to mocno oparte na wersji 2D i ma podobne cechy wydajności.

W dokumencie Ruch. Podstawy algorytmów ruchu (Stron 135-142)

Powiązane dokumenty