Od wersji 36.00 UEFN używa układu współrzędnych LUF w edytorze i wszystkich przekształceniach modułu /Verse.org. Przekształcenia w modułach /UnrealEngine.com i /Fortnite.com wykorzystują system współrzędnych XYZ.
Przedstawiamy układ współrzędnych LUF
Wszystkie główne narzędzia do tworzenia treści 3D do pozycjonowania i obracania obiektów wykorzystują kartezjański układ współrzędnych (X, Y i Z). Jednak poszczególne narzędzia mają różne interpretacje, które osie (X, Y i Z) reprezentują poszczególne kierunki (np. lewo/prawo, góra/dół oraz przód/tył). Ponadto sposób modelowania obrotów – tzw. ręczność układu współrzędnych – również różni się w zależności od narzędzia.
Aby lepiej dostosować Verse i UEFN do pojawiających się standardów w tworzeniu treści 3D i innych popularnych zestawów narzędzi, wprowadzamy zasadnicze zmiany w naszej prezentacji układu współrzędnych.
Po pierwsze, zamiast oznaczać osie współrzędnych literami X, Y i Z, wprowadzamy bardziej opisowe nazwy osi:
Left (Lewa) (było -Y)
Up (Do góry) (było Z)
Forward (przednia) (było X)
Zmiana z poprzednich XYZ i FRU na LUF odpowiada zatem zmianie orientacji osi z leworęcznego układu współrzędnych (XYZ i FRU) na praworęczny układ współrzędnych (LUF).
Po drugie, kolory gizmo okna widoku UEFN zostały zmienione, aby dostosować je do innych programów do tworzenia treści 3D:
Left (Lewa): czerwony (był zielony)
Up (Do góry): Zielony (był niebieski)
Forward (przednia): niebieski (był czerwone)
Po trzecie, w /Verse.org/SpatialMath module:
Wszystkie funkcje modułu obliczeń przestrzennych w Verse są teraz operacjami praworęcznymi. W rezultacie wszystkie funkcje z jawnymi wariantami praworęczności zostały usunięte i zastąpione wersjami, które domyślnie służą do wykonywania operacji praworęcznych, a z nazw funkcji usunięto wskaźniki praworęczności. Aby uzyskać więcej informacji, patrz sekcja Praworęczność poniżej na tej stronie.
Funkcje obrotu, które wymagają parametru kąta lub wartości zwracanej, są teraz dostępne w wersjach przyjmujących lub zwracających radiany lub stopnie, i są one oznaczone przyrostkami
Radians (radiany)lubDegrees (stopnie)dla odpowiedniego wariantu.Usunięto funkcje RotateBy,UnrotateByoraz wszystkie(:rotation).Apply*, a zamiast tego wprowadzono operator mnożenia, który łączy dwaobroty odlewej do prawej. Więcej informacji znajduje się w sekcji [Kolejność mnożenia transformacji i rotacji]() na tej stronie.Usunięto
RotateVectoriUnrotateVector, a zamiast tego wprowadzono operator mnożenia, który obracavector3oobrótw podanej kolejności. Aby dowiedzieć się więcej, patrz sekcja Zmiany operacji matematyki przestrzennej na tej stronie.Usunięto
TransformVectoriTransformVectorNoScalena rzecz operatora mnożenia, który przekształcavector3poprzezprzekształceniew następującej kolejności: skala, obrót, translacja. Aby dowiedzieć się więcej, patrz sekcja Zmiany operacji matematyki przestrzennej na tej stronie.
Po czwarte, domyślne importy siatek szkieletowych z FBX używają teraz opcji wyrównania osi Up i Forward, tak aby wszystkie zaimportowane siatki szkieletowe były skierowane wzdłuż osi Forward.
Na co to ma wpływ?
Ta zmiana układu współrzędnych dotyczy każdego, kto używa UEFN lub przekształceń w module /Verse.org, w szczególności kilka aspektów UEFN i Verse, które obejmują:
Panel szczegółów UEFN
Okno wizualizacji i gizmo UEFN
Przekształcenie Verse
Zmiana kodu lub treści nie jest konieczna do prawidłowego funkcjonowania waszych opublikowanych wysp w LUF.
Panel szczegółów UEFN
Przekształcenia w Panelu szczegółów UEFN wyświetlają teraz współrzędne w formie systemu LUF.
Okno wizualizacji UEFN
Zmieniono mapowanie między osiami i kolorami gizmo w oknie wizualizacji UEFN.
Przekształcenia Verse
Przekształcenia modułu /Verse.org używają systemu LUF . Przekształcenie modułu /UnrealEngine.com wykorzystujące układ współrzędnych XYZ nadal istnieje i jest używane jako domyślne przekształcenie w modułach /UnrealEngine.com i /Fortnite.com.
Oba te moduły zawierają typy przekształceń, dlatego pamiętaj o następujących kwestiach:
Jeśli używasz funkcji API, które wykorzystują zarówno transformacje modułu
/Verse.org, jak i transformacje modułu/UnrealEngine.comw tym samym pliku, nazwy typów muszą być określone przez ich ścieżkę, aby uniknąć niejednoznaczności między dwoma modułami. Pokazuje to poniższy przykładowy fragment.VerseNazwy typów kwalifikowane przez ścieżkęusing { /UnrealEngine.com/Temporary/SpatialMath } using { /Verse.org/SpatialMath } my_class := class: MyUnrealEngineVector:(/UnrealEngine.com/Temporary/SpatialMath:)vector3 = (/UnrealEngine.com/Temporary/SpatialMath:)vector3{} MyVerseVector:(/Verse.org/SpatialMath:)vector3 = (/Verse.org/SpatialMath:)vector3{}Scene Graph wykorzystuje przekształcenia modułu
/Verse.org. To oznacza, że Scene Graph używa teraz tylko układu współrzędnych LUF.Podsumowanie Verse używa w pełni kwalifikowanych identyfikatorów Verse do rozróżniania typów transformacji zdefiniowanych w modułach UnrealEngine.com/Temporary/SpatialMath i Verse.org/SpatialMath.
Aby dowiedzieć się więcej o systemie współrzędnych Unreal Engine, patrz: System współrzędnych i przestrzenie w Unreal Engine.
Konwertowanie z XYZ na LUF
Jeśli używasz istniejących funkcji API, które zostały przełączone na transformacje modułu /Verse.org (LUF), konieczne jest przekonwertowanie zdefiniowanych przez użytkownika transformacji /UnrealEngine.com (XYZ), aby używać LUF lub nowo utworzonych funkcji konwersji FromTransform. W tej sekcji znajdziesz kilka potencjalnych problemów z konwersją oraz ich rozwiązań.
Niejednoznaczność typu stałej lub zmiennej
W związku z ostatnimi zmianami w interfejsie API Verse zarówno /UnrealEngine.com/Temporary/SpatialMath, jak i /Verse.org/SpatialMath definiuje typy vector3, obrotu i przekształcenia. W rezultacie uwzględnienie obu domen w pliku Verse spowoduje niejednoznaczność typów.
Przykład niejednoznaczności typu
Następujący plik Verse importuje zarówno ścieżkę modułu /UnrealEngine.com/Temporary/SpatialMath, jak i ścieżkę modułu /Verse.org/SpatialMath, a klasa zdefiniowana przez użytkownika używa typu niekwalifikowanego vector3:
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/SpatialMath }
my_class := class:
MyVectorOne:vector3 = vector3{}
#Compile Error: Identifier vector3 could be one of many types: UnrealEngine.com.Temporary.SpatialMath.vector3 or Verse.org.SpatialMath.vector3
MyVectorTwo:vector3 = vector3{}
#Compile Error: Identifier vector3 could be one of many types: UnrealEngine.com.Temporary.SpatialMath.vector3 or Verse.org.SpatialMath.vector3W rezultacie niejednoznaczność typu powoduje błąd kompilacji podczas próby skompilowania tego pliku, ponieważ kompilator nie wie, czy skompilować MyVectorOne i MyVectorTwo jako typ /UnrealEngine.com vector3, czy jako typ /Verse.org vector3. Użytkownik nie podał kompilatorowi wystarczających informacji, aby mógł on rozpoznać, który typ jest wskazany.
Aby rozwiązać ten błąd kompilacji, musisz określić ścieżkę dostępu do typu vector3 używanego przez każdą zmienną. Istnieje kilka różnych sposobów kwalifikowania ścieżek tych stałych w celu rozwiązania błędu:
Możesz w pełni zakwalifikować dowolną stałą lub zmienną typu vector3, jak pokazano w poniższym fragmencie kodu:
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org/SpatialMath }
my_class := class:
MyVectorOne:(/UnrealEngine.com/Temporary/SpatialMath:)vector3 = (/UnrealEngine.com/Temporary/SpatialMath:)vector3{}
MyVectorTwo:(/Verse.org/SpatialMath:)vector3 = (/Verse.org/SpatialMath:)vector3{}Możesz zakwalifikować stałą lub zmienną z modułu /Verse.org, jak pokazano w poniższym fragmencie kodu:
using { /UnrealEngine.com/Temporary/SpatialMath }
using { /Verse.org } # Change the module path to avoid import ambiguity
my_class := class:
MyVectorOne:vector3 = vector3{}
MyVectorTwo:SpatialMath.vector3 = SpatialMath.vector3{} # Specify the submoduleMożesz zakwalifikować stałą lub zmienną z modułu /UnrealEngine.com, jak pokazano w poniższym fragmencie kodu:
using { /UnrealEngine.com/Temporary } # Change the module path to avoid import ambiguity
using { /Verse.org/SpatialMath }
my_class := class:
MyVectorOne:SpatialMath.vector3 = SpatialMath.vector3{} # Specify the submodule
MyVectorTwo:vector3 = vector3{}Zmiana typu Scene Graph
Jeśli używasz interfejsów API Scene Graph w plikach Verse i nie korzystasz z innych interfejsów API wykorzystujących matematykę przestrzenną, możesz zmienić typy matematyki przestrzennej w kodzie Verse, zmieniając moduł matematyki przestrzennej importowany za pomocą słowa kluczowego using. Na przykład, jeśli masz następujący kod:
using { /UnrealEngine.com/Temporary/SpatialMath }
my_component := class<final_super>(component):
@editable # This change will work regardless of this field being editable or not
MyVector3:vector3 = vector3{}
MyVector2:vector2 = vector2{}Możesz to zmienić na:
using { /Verse.org/SpatialMath } # Changed the module path
my_component := class<final_super>(component):
@editable # This change will work regardless of this field being editable or not
MyVector:vector3 = vector3{} # All transform types now use /Verse.org/SpatialMath
# MyVector2:vector2 = vector2{} Removed since /Verse.org/SpatialMath does not define a vector2Jedynym wyjątkiem jest to, że wszystkie typy vector2 muszą zostać usunięte lub przekonwertowane na vector3, ponieważ moduł /Verse.org nie definiuje obecnie typu vector2.
Niezgodność typu funkcji
Innym scenariuszem, w którym może zaistnieć potrzeba kwalifikowania typów przy użyciu ścieżki lub konwersji kodu, jest sytuacja, gdy:
Wywołana funkcja lub przypisana zmienna oczekuje typu z modułu
/UnrealEngine.com, ale otrzymuje typ z modułu/Verse.org.Wywołana funkcja lub przypisana zmienna oczekuje typu z modułu
/Verse.org, ale otrzymuje typ z modułu/UnrealEngine.com.
Niezgodność typu funkcji – przykład
W tym samym projekcie istnieją dwa pliki Verse. FileOne.verse definiuje funkcję służącą do wyświetlania transformacji z modułu /UnrealEngine.com/Temporary/SpatialMath. FileTwo.verse definiuje stałe przekształcenie z /Verse.org/SpatialMath oraz funkcję wyświetlającą tę wartość, która wywołuje funkcję z FileOne.verse.
using { /UnrealEngine.com/Temporary/SpatialMath }
PrintTransform(T:transform):void=
Print("T: {T}")using { /Verse.org/SpatialMath }
my_class := class:
T:transform = transform{}
DoPrint():void=
PrintTransform(T) # Compile error: This function parameter expects a value of type (/UnrealEngine.com/Temporary/SpatialMath:)transform, but this argument is an incompatible value of type (/Verse.org/SpatialMath:)transform.Powoduje to błąd kompilacji, ponieważ typy transformacji odwołują się do dwóch różnych typów transformacji zdefiniowanych w różnych modułach.
Aby naprawić ten błąd kompilacji, musisz przekonwertować typ transformacji na prawidłowy typ transformacji z odpowiedniego modułu. Edytuj kod w pliku FileTwo.verse, aby użyć funkcji FromTransform, która konwertuje transformację z /Verse.org/SpatialMath na transformację z /UnrealEngine.com/Temporary/SpatialMath.
Pełna lista dostępnych funkcji konwersji znajduje się w następnej sekcji.
# Include /UnrealEngine.com to access conversion functions
using { /UnrealEngine.com }
using { /Verse.org/SpatialMath }
my_class := class:
T:transform = transform{}
DoPrint():void=
PrintTransform(Temporary.SpatialMath.FromTransform(T)) # Compile error fixed after converting transform to the correct type from the proper moduleKonwersja typu wektora, obrotu i przekształcenia
Moduł /UnrealEngine.com udostępnia kilka nowych funkcji konwersji, które umożliwiają konwersję między potencjalnie niejednoznacznymi typami zdefiniowanymi w module /UnrealEngine.com a modułem /Verse.org.
Od matematyki przestrzennej Unreal Engine do matematyki przestrzennej Verse
# Util function for converting a `vector3` from /UnrealEngine.com/Temporary/SpatialMath to a `vector3` from /Verse.org/SpatialMath.
FromVector3<public>(InVector3:(/UnrealEngine.com/Temporary/SpatialMath:)vector3)<reads>:(/Verse.org/SpatialMath:)vector3
# Util function for converting a `rotation` from /UnrealEngine.com/Temporary/SpatialMath to a `rotation` from /Verse.org/SpatialMath
FromRotation<public>(InRotation:(/UnrealEngine.com/Temporary/SpatialMath:)rotation)<reads>:(/Verse.org/SpatialMath:)rotation
# Util function for converting a `transform` from /UnrealEngine.com/Temporary/SpatialMath to a `transform` from /Verse.org/SpatialMath.
FromTransform<public>(InTransform:(/UnrealEngine.com/Temporary/SpatialMath:)transform)<reads>:(/Verse.org/SpatialMath:)transform
Od matematyki przestrzennej Verse do matematyki przestrzennej Unreal Engine
# Util function for converting a `vector3` from /Verse.org/SpatialMath to a `vector3` from /UnrealEngine.com/Temporary/SpatialMath.
FromVector3<public>(InVector3:(/Verse.org/SpatialMath:)vector3)<reads>:(/UnrealEngine.com/Temporary/SpatialMath:)vector3
# Util function for converting a `rotation` from /Verse.org/SpatialMath to a `rotation` from /UnrealEngine.com/Temporary/SpatialMath.
FromRotation<public>(InRotation:(/Verse.org/SpatialMath:)rotation)<reads>:(/UnrealEngine.com/Temporary/SpatialMath:)rotation
# Util function for converting a `transform` from /Verse.org/SpatialMath to a `transform` from /UnrealEngine.com/Temporary/SpatialMath.
FromTransform<public>(InTransform:(/Verse.org/SpatialMath:)transform)<reads>:(/UnrealEngine.com/Temporary/SpatialMath:)transform
Operacje i funkcje matematyczne przestrzenne w Verse
Praworęczność
Wszystkie funkcje modułu obliczeń przestrzennych w Verse są teraz operacjami praworęcznymi. W rezultacie wszystkie funkcje z jawnymi wariantami ręczności zostały usunięte i zastąpione wersjami, które domyślnie służą do wykonywania operacji praworęcznych, a z nazw funkcji usunięto wskaźniki ręczności. Wszystkie operacje, przy których istnieje możliwość wyboru ręczności, takie jak mnożenie vector3 przez obrót lub mnożenie vector3 przez przekształcenie, są operacjami praworęcznymi.
Na przykład poniższy kod ilustruje, że wykonanie obrotu o dodatnie 90 stopni wokół osi Up (górna) i zastosowanie tego obrotu do wektora jednostki Forward (przednia) daje w wyniku wektor jednostki Left (w lewo):
ForwardToLeft:rotation = MakeRotationFromEulerDegrees(0.0, 90.0, 0.0)
ForwardVector:vector3 = vector3{Forward := 1.0}
ResultantVector:vector3 = ForwardVector * ForwardToLeft # Apply rotation on the right of the multiplication operator
Print("{ResultantVector}") # {Forward = 0.000000, Left = 1.000000, Up = 0.000000} = {Forward = 1.000000, Left = 0.000000, Up = 0.000000} * {Axis = {Forward = 0.000000, Left = 0.000000, Up = 1.000000}, Angle = 90.000000}
Kolejność mnożenia przekształcenia i obrotu
Pojęcie obrotów należy rozumieć jako obroty wewnętrznie reprezentowane przez macierz. Gdy do wektora v zastosowano obrót R, są one wykonywane zgodnie z kolejnością mnożenia wierszy/wektorów. Oznacza to, że wektor v jest wektorem wiersza, a gdy wektor jest obracany, mnożenie jest stosowane po prawej stronie w celu uzyskania wyniku, v' = v * R. W tym przykładzie wypadkowy wektor v' jest nowym wektorem wierszowym. Działa to podobnie w przypadku zastosowania Przekształcenie T do wektora v, v' = v * T, a wynikowy wektor v' jest innym wektorem wiersza.
Kolejność ma znaczenie przy stosowaniu obrotów i przekształceń do wektorów. Trzymaj obiekt, na przykład książkę, w dłoni wierzchem do góry i awersem/okładką do siebie. Zastosowanie praworęcznego obrotu o dodatnie 90 stopni w wokół osi Forward (przednia) powoduje, że górna część książki jest teraz skierowana w prawo. Następnie zastosowanie obrotu o 90 stopni wokół osi Left (lewa) spowoduje, że przód telefonu będzie skierowany w górę, a górna część książki będzie skierowana w prawo.
Teraz zastosuj te same obroty w odwrotnej kolejności. Zastosowanie obrotu o 90 stopni wokół osi Left (lewa) spowoduje, że góra książki będzie skierowana do przodu, a przód książki będzie skierowany do góry. Następnie, zastosowanie obrotu o 90 stopni wokół osi Forward (przednia) spowoduje, że góra książki będzie skierowana do przodu, a przód książki będzie skierowany w prawo. Nie jest to ta sama orientacja, co zastosowanie obrotów w drugiej kolejności ilustrujące, że kolejność obrotów ma znaczenie.
Mając to na uwadze, jeśli chcesz zastosować dwa obroty R i R' do wektora v, musisz zadbać o to, aby zastosować je w żądanej kolejności. Jeśli chcesz zastosować obrót R przed obrotem R', wówczas odpowiednia kolejność to v' = v * R * R'.
Ta sama zasada dotyczy samych obrotów. Jeśli masz obrót R i chcesz zastosować przed obrotem PreR do R, który ma miejsce przed obrotem R, oraz PostR po obrocie, który ma miejsce po obrocie R, odbywa się to w kolejności PreR * R * PostR. Podobnie, jeśli chcesz zastosować przed przekształceniem PreT i po przekształceniu PostT do przekształcenia T, odbywa się to w kolejności PreT * T * PostT. Następnie zastosowanie ich do wektora v w celu uzyskania nowego wektora v' odbywa się jak powyżej, v' = v * PreR * R * PostR lub v' = v * PreT * T * PostT.
Łączność (kolejność wykonywania) transformacji i obrotów również ma znaczenie. Jeśli chcesz zastosować wiele operacji na wektorze w tym samym wierszu kodu Verse, na przykład przekształcenie, po którym nastąpi obrót, powiązanie operacji ma znaczenie. Na przykład możesz spróbować:
ForwardVector:vector3 = vector3{Forward := 1.0}
ForwardToLeftUpOne:transform = transform:
Translation := vector3{Up := 1.0}
Rotation := MakeRotationFromEulerDegrees(0.0, 90.0, 0.0)
UpToForward:rotation = MakeRotationFromEulerDegrees(90.0, 0.0, 0.0)
ResultantVector:vector3 = ForwardVector * ForwardToLeftUpOne * UpToForward # compilesProces kompilacji przebiega prawidłowo, ponieważ niejawnie kojarzy operacje jako:
ResultantVector:vector3 = (ForwardVector * ForwardToLeftUpOne) * UpToForward # compiles
Jeśli jednak w sposób jawny spróbujesz powiązać inaczej jako:
ResultantVector:vector3 = ForwardVector * (ForwardToLeftUpOne * UpToForward) # error, no operator'*'(:transform,:rotation)
Powoduje to błąd. Aby uniknąć tych błędów, jawnie powiąż pierwszą operację między wektorem a przekształceniem lub wektorem i obrotem.
ResultantVector:vector3 = (ForwardVector * ForwardToLeftUpOne) * UpToForward # explicitly associated
Zmiany operacji matematycznych przestrzennych
Obrót i mnożenie obrotu
RotateBy i UnrotateBy zostały usunięte i zastąpione operatorem mnożenia dla dwóch obrotów.
ForwardToLeft:rotation = MakeRotationFromEulerDegrees(0.0, 90.0, 0.0)
UpToForward:rotation = MakeRotationFromEulerDegrees(90.0, 0.0, 0.0)
UpToLeft:rotation = UpToForward * ForwardToLeft # RotateBy, correcting for handednessAby odwrócić obrót („cofnąć obrót”), najpierw należy odwrócić obrót, a następnie pomnożyć obroty. Aby odzyskać obrót UpToForward z UpToLeft, odwróć obrót ForwardToLeft i zastosuj go z prawej strony do obrotu UpToLeft. Poniżej przedstawiono krok po kroku sposób, w jaki można to osiągnąć.
# The following comments show the steps involved to reach the desired result.
# This involves multiplication on the right by the inverse, associativity, existence and definition of inverse rotation, and existence and definition of identity rotation
# Pseudocode Steps:
# UpToLeft * ForwardToLeft.Invert() = (UpToForward * ForwardToLeft) * ForwardToLeft.Invert() # multiply on the right by the same expression on both sides of the equality operator
# UpToLeft * ForwardToLeft.Invert() = UpToForward * (ForwardToLeft * ForwardToLeft.Invert()) # reassociate
# UpToLeft * ForwardToLeft.Invert() = UpToForward * IdentityRotation() # rotation * inverse = identity
# UpToLeft * ForwardToLeft.Invert() = UpToForward # rotation * identity = rotation
UpToForwardAgain:rotation = UpToLeft * ForwardToLeft.Invert() # UnrotateBy, correcting for handednessVector3 i mnożenie po obrocie
Podobnie usunięto operacje Rotate (obrót) i Unrotate (cofnięcie obrotu) i zastąpiono je operatorem mnożenia dla vector3 obróconego w prawo o obrót.
ForwardVector:vector3 = vector3{Forward:=1.0}
ForwardToLeft:rotation = MakeRotationFromEulerDegrees(0.0, 90.0, 0.0)
LeftVector:vector3 = ForwardVector * ForwardToLeft # Rotate, correcting for handednessAby odwrócić obrót (cofnąć obrót), najpierw należy go odwrócić, a następnie pomnożyć przez wektor. Aby odzyskać ForwardVector z LeftVector, odwróć obrót ForwardToLeft i zastosuj go z prawej strony do LeftVector.
# The steps involved in achieving this calculation are very similar to the steps shown above for obtaining the correct expression for unrotating by a rotation
ForwardVectorAgain:vector3 = LeftVector * ForwardToLeft.Invert() # Unrotate, correcting for handednessVector3 i mnożenie przekształcenia
Usunięto TransformVector i TransformVectorNoScale i zastąpiono je operatorem mnożenia dla vector3 przekształcanego z prawej strony za pomocą przekształcenia.
DoubleLengthForwardToLeftTranslateUp := transform:
Translation := vector3{Left := 0.0, Up := 1.0, Forward := 0.0}
Rotation := MakeRotationFromEulerDegrees(0.0, 90.0, 0.0)
Scale := vector3{Left := 2.0, Up := 2.0, Forward := 2.0}
ForwardVector:vector3 = vector3{Forward:=1.0}
ResultantVector:vector3 = ForwardVector * DoubleLengthForwardToLeftTranslateUp # TransformVectorAby wykonać przekształcenie bez skalowania, TransformVectorNoScale, pozostaw domyślną wartość skali w tworzonym przekształceniu.
ForwardToLeftTranslateUpNoScale := transform:
Translation := vector3{Left := 0.0, Up := 1.0, Forward := 0.0}
Rotation := MakeRotationFromEulerDegrees(0.0, 90.0, 0.0)
ForwardVector:vector3 = vector3{Forward:=1.0}
ResultantVector:vector3 = ForwardVector * ForwardToLeftTranslateUpNoScale # TransformVectorNoScale