Developedia
Developedia
Egyéb vektorműveletek

Egyéb vektorműveletek

Korábban megismerkedtünk már néhány vektor művelettel (2D és 3D vektorok2D és 3D vektorok ) és láttuk, hogy a teljes játékfejlesztés ezen vektorok körül forog. Most nézzünk át pár újabb hasznos függvényt!

A visszaverés / visszapattanás

Ahogy a neve is mutatja ez a művelet, azt mondja meg, hogy merre pattanna egy fénysugár egy megadott normálvektorú felületről

A művelet 2 és 3 dimenzióban is értelmezett

image
Vector3 inDirection;     // A bemenő sugár iránya
Vector3 surfaceNormal;   // A felület normálvektora

Vector3 reflected = Vector3.Reflect(inDirection, surfaceNormal);

Vektor szorzás

Korábban megismertünk már szorzás műveletet vektoron, ám ekkor egy vektort szoroztunk egy skalárral. Most nézzük meg milyen egyéb definiált “szorzásművelet” létezik a vektorokon:

Magyer név
Angol név
Bemenetek
Kimenet
Szorzás skalárral
Scalar Multiplication
Skalár, Vector
Vector
Skaláris szorzás
Dot product
2 Vector
Skalár
Vektoriális szorzás
Cross product
2 Vector
Vector
Elemenkénti szorzás
Hadamard product
2 Vector
Vector

Dot Product - Skaláris szorzás

Skaláris szorzat vét vektor közt elvégezhető művelet, aminek eredménye egy szám.

A Dot Product neve onnan szármozik, hogy a pont operátorral szoktuk jelölni a matematikában, szemben a skaláris szorzással, amikor a kereszt szimbólumot használjuk:

Dot product:

f=v1⋅v2f = v1 · v2f=v1⋅v2

Cross Product:

f=v1×v2f = v1 × v2f=v1×v2.

A fenti jelöléstan matematikában használatos, Unity-ben csak a már megismert skalárral történő szorzásra van dedikált operátor: * , helyette egy függvényhíváson keresztül érhető el a művelet.

float dot = Vector3.Dot(a, b)  // a és b Vecor3 típusú változók

A magyar “Skaláris szorzás” név pedig a függvény kimenetére utal, ami egy skalár. Tehát ha két vektoron skaláris szorzást hajtunk végre, egy számot kapunk.

Kétféle módin is kiszámolható ez a szám:

A⋅B=Ax⋅Bx+Ay⋅ByA⋅B=∣A∣⋅∣B∣⋅cosΘA · B = A_x·B_x+A_y·B_y\\A · B = |A|·|B|·cosΘA⋅B=Ax​⋅Bx​+Ay​⋅By​A⋅B=∣A∣⋅∣B∣⋅cosΘ

Ahol AAA és BBB vektorok,

Ax,Ay,Bx,ByA_x, A_y, B_x, B_yAx​,Ay​,Bx​,By​ a vektor egyes komponensei,

∣A∣|A|∣A∣ és ∣B∣|B|∣B∣ a vektorok abszolút értékei (hosszai) (magnitude)

ΘΘΘ pedig a a két vektor által bezárt szög.

Ebből felírható tehát:

∣A∣⋅∣B∣⋅cosΘ=Ax⋅bx+Ay⋅By∣A∣⋅cosΘ=A⋅B∣B∣cosΘ=A⋅B∣A∣⋅∣B∣|A|·|B|·cosΘ = A_x·b_x+A_y·B_y \\ |A|·cosΘ = \frac{A · B}{|B|}\\ cosΘ = \frac{A · B}{ |A|·|B|}∣A∣⋅∣B∣⋅cosΘ=Ax​⋅bx​+Ay​⋅By​∣A∣⋅cosΘ=∣B∣A⋅B​cosΘ=∣A∣⋅∣B∣A⋅B​

Ez azért hasznos, mert (∣A∣⋅cosΘ)(|A| · cosΘ)(∣A∣⋅cosΘ) pedig megfelel a AAA vektor BBB vektorra történt vetítésével, és így gyorsan megkaphatjuk a vetített vektor hosszát.

Látsd a képen:

A vektor “vetítése” B vektora
A vektor “vetítése” B vektora
icon
Tehát a skaláris szorzás két felhasználása:
  1. Két vektor közti szög meghatározása
  2. Egy vektor vetítésre egy másikra

A skaláris szorzás működik 2D, 3D (és bárhány nagyobb dimenziós) vektoron is sőt a műveletre lehet úgy gondolni, mint a hagyományos szorzás több dimenziós kiterjesztésére.

  • Ha a vektorok teljesen egy irányba mutatnak, akkor a skaláris szorzat szimplám egyenlő a két vektor hosszának (abszolút értékének) szorzatával. (Nevezzük ez mmm-nek: m=∣A∣⋅∣B∣m = |A| · |B|m=∣A∣⋅∣B∣)
  • Ha a vektorok pontosan egymásnak ellenkező irányba mutatnak, akkor a skaláris szorzat a vektorok hosszának (abszolút értékének) szorzata lesz a negatív tartományban. (Tehát −m-m−m)

ha végiggondoljuk a fentieket, a jól megszokott normál szorzás ugyanígy viselkedik az 1 dimenziós vektorokra, azaz a számegyene számaira.

A különbség akkor van, ha a két vektor valamekkora (nem 0 vagy 180 fokos) szöget zár be egymásra, hisz ilyen az egy dimenziós számegyenesen nem lehetséges. Ekkor ha a vektorok közti szög…

  • Hegyes ( -90 < α < 90 ), akkor a skaláris szorzat pozitív szám, ami kisebb, mint mmm.
  • Tompa ( 90 < α < 270 ), akkor a skaláris szorzat negatív szám , ami nagyobb, mint −m-m−m.

Két vektor közti szög

A fentiek alapján skalárszorzással kiszámolható két vektor közt bezárt szög:

Vector2 a, b;
// ...
float dot = Vector3.Dot(a, b)
float cosAngle = dot / (a.magnitude * b.magnitude);
float angleInRad = Mathf.Acos(cosAngle);
float angleInDeg = Mathf.Rad2Deg * angleInRad;

Mindez azonban másik Unity függvényekkel egy sorban is elvégezhető:

Egy vektor vetítésre egy másikra

A fentiek alapján:

Vector2 a, b;
// ...
float dot = Vector3.Dot(a, b)
float projectedLength = dot / b.magnitude;
Vector2 projectedVector = projectedLength * b.normalized;

Mindez azonban másik Unity függvényekkel rövidebben is elvégezhető:


// "a" vetítése "b"-re
Vector2 projected = Vector2.Project(a2, b2);  // b2 vektor hossza nem számít
Vector3 projected = Vector3.Project(a3, b3);  // b3 vektor hossza nem számít

Skaláris szorzás haszna

Unity-ben tehát van beépített függvény vektorok közti bezárt szög megkapására és vetítésre is. Miért mondtam akkor el, hogyan működik a dot product. Egyfelől azért, mert ez mindig hasznos informatikai-matematikai tudás, a beépített megoldások mindegyike is a háttérben garantáltan skaláris szorzást használ. Másfelől azért, mert a művelet ismeretével erőforrás-kritikus esetekben sokat optimalizálhatunk. Nézzünk erre példákat:

Bezárt szög megállapítása

Ha két vektor közti szögnek csak a koszinusza vagy a radiánban vett értéke érdekel vagy egyszerűen az, hogy tompa/hegyes vagy derék szöget zár be, akkor érdemes lehet csak dot product-ot használni.

Előbbi esetre példa lehet, ha csak azt akarjuk tudni, hogy a bezárt szög hegyes vagy tompa.

Vector3 forward = transform.TransformDirection(Vector3.forward);
Vector3 toOther = otherTransform.position - transform.position;

if (Vector3.Dot(forward, toOther) < 0)
	Debug.Log("The other transform is behind me!");  //Tompaszög: Mögöttem van

Vetítés

Vetítés optimalizálható, ha a vektor amire vetítünk már eleve egy egységvektor (1 hosszú vektor).

Vector2 a, b;
// ...
float dot = Vector3.Dot(a, b)
float bMagnitude = b.magnitude;
Vector2 projectedVector =  b * dot / (bMagnitude * bMagnitude);

// Tehát ha tudjuk, hogy "b" egységvektor,
// akkor a magnitude-del történő osztás elhagyható:
projectedVector =  b * dot;

Emlékezzünk, skalár szorzás kiszámolható egyszerűen float összeadás és szorzás műveletekkel.

Tehát így nagyon olcsón el tudtuk végezni a vetítést. A Vector2.Project és Vector3.Project ekkor jóval lassabb lenne.

Cross Product - Vektoriális szorzás

A vektoriális szorzás csak 3D-ben értelmezett. Két 3D vektor bemenete van és egy 3D vektor kimenete. A vektoriális szorzás azt a vektort adja vissza, ami merőleges mindkét bemenetre, paraméter vektorra.

A két bemeneti vektor nem kell, hogy merőleges legyen, csak, hogy ne legyenek párhuzamosak.

Két irányvektor is teljesíti a fenti feltételt, hogy ezek közül melyiket adja vissza a művelet, azt a paraméterek sorrendje szabályozza. Bal kéz szabállyal gondolhatjuk végig fejben, milyen irányú vektort fog visszaadni a művelet.

Bal kész szabály
Bal kész szabály

Az eredményvektor hossza: ∣A∣⋅∣B∣⋅sinΘ|A|·|B|·sinΘ∣A∣⋅∣B∣⋅sinΘ, ám a legtöbb esetben ez nem érdekel minket, csak az eredmény vektor normalizáltja azaz a két paraméter vektorra merőleges irány.

Vector3 a, b;
// ...
Vector3 perpendicular1 = returnVector3.Cross(a, b).normalized;
Vector3 perpendicular2 = returnVector3.Cross(b, a).normalized;

A Kereszt szorzás eredménye a skalárishoz hasonlóan szintén megkapható egyszerű szorzások és összeadások alapján. A megfelelő képlet lent látható, de ennek részleteit itt nem tárgyaljuk.

a×b=ccx=aybz−azbycy=azbx−axbzcz=axby−aybx{a\times b} = c \\ c_{x} = a_{y}b_{z}-a_{z}b_{y} \\ c_{y} = a_{z}b_{x}-a_{x}b_{z} \\ c_{z} = a_{x}b_{y}-a_{y}b_{x}a×b=ccx​=ay​bz​−az​by​cy​=az​bx​−ax​bz​cz​=ax​by​−ay​bx​

Merőleges 2D vektorra

Mint láttuk, 3D vektorok esetén merőlegest a vektoriális szorzás segítségével kapunk.

Ehhez két különböző 3D-s vektorra is szükségünk van, hiszen három dimenziós térben egy irányra végtelen egyéb merőleges irány létezik. Ezzel szemben 2D síkon egy nyílhoz csak kettő egyéb merőlegest tudunk felrajzolni, az eredetihez képest 90 fokban jobbra és a 90 fokban balra mutatót.

Ezek természetesen megkaphatók lennének, ha 3D-be alakítanánk az eredeti vektort, majd azt vektoriálisan szoroznánk az előre (0,0,1) valamit hátra (0,0,-1) mutató irányvektorral, és végül vissza-kasztolnánk Vector2-be. Ám kettő dimenzióban ennél sokkal egyszerűbb számítás is létezik:

Vector2 v;   // Eredeti vektor
//...
Vector2 perpendicularRight = new(vy, -vx);   // Elforgatva jobbra 90 fokkal
Vector2 perpendicularLeft  = new(-vy, vx);   // Elforgatva balra 90 fokkal
Vector2 opposite = -v;                       // Ellentétes irány

Hadamard product - Elemenkénti szorzás

A legegyszerűbb vektorokon végzett szorzásművelet. Egyszerűen szorozza az egyes komponenseket, a párjukkal

Vector3 a, b;
//...

Vector3 scaled = Vector3.Scale(a,b);

// Manuálisan mindez így nézne ki
Vector3 scaled = new Vector3(a.x * b.x ,a.y * b.y, a.z * b.z);

Leggyakrabban erre akkor van szűkség, ha több tengely mentén egyszerre szeretnénk méretezni egy vektort: Pl.:

Vector3 a = new Vector3(12.4f, 3.1f, 22.9f);
// Méretezés 
Vector3 scaled =  Vector3.Scale(a, new Vector3(5,-1,3));
Logo

Főoldal

Blog

Elmélet

3D Studio

Adatvédelmi nyilatkozat

GY.I.K.

Házirend

Szerző: Marosi Csaba / marosi.csaba@3d-studio.hu

DiscordGitHubLinkedIn
float angleInDeg = Vector2.Angle(a2, b2);
float angleInDeg = Vector3.Angle(a3, b3);
// Az eredmény mindig 0 és 180 közt lesz
// Eredmény fokban

float angleInDeg = Vector2.SignedAngle(a2, b2);
float angleInDeg = Vector3.SignedAngle(a3, b3);
// Szög óramutató járása szerint a-ból b-be értendő
// Az eredmény mindig -180 és 180 közt lesz
// Eredmény fokban

// Például
Vector2 a = Vector2.up;
Vector2 b = Vector2.right;

float angle1 = Vector2.Angle(a, b);  // 90°
float angle2 = Vector2.Angle(b, a);  // 90°

float signedAngle1 = Vector2.SignedAngle(a, b);  // 90°
float signedAngle2 = Vector2.SignedAngle(b, a);  // -90°