Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Matrici e trasformazioni

Introduzione

Prima di leggere questo tutorial, ti consigliamo di leggere e comprendere il tutorial Matematica vettoriale a fondo, siccome questo tutorial richiede una conoscenza sui vettori.

Questo tutorial riguarda le trasformazioni e come rappresentarle in Godot tramite matrici. Non è una guida completa e approfondita sulle matrici. Le trasformazioni sono molto spesso applicate sotto forma di traslazione, rotazione e scala, quindi ci concentreremo su come rappresentare quest'ultime con le matrici.

La maggior parte di questa guida si concentra sul 2D, utilizzando Transform2D e Vector2, ma le operazioni in 3D sono molto simili.

Nota

Come menzionato nel tutorial precedente, è importante ricordare che in Godot, l'asse Y punta verso il basso in 2D. Questo è l'opposto di come la maggior parte delle scuole insegna l'algebra lineare, con l'asse Y rivolto verso l'alto.

Nota

La convenzione prevede che l'asse X sia rosso, l'asse Y sia verde e l'asse Z sia blu. Questo tutorial è codificato a colori per rispettare queste convenzioni, ma rappresenteremo anche il vettore origine con il colore blu.

Componenti della matrice e la matrice di identità

La matrice di identità rappresenta una trasformazione senza traslazione, rotazione e scala. Cominciamo esaminando la matrice di identità e il modo in cui le sue componenti influenzano il suo aspetto visivo.

../../_images/identity.png

Le matrici sono composte da righe e colonne, e una matrice di trasformazione ha convenzioni specifiche su cosa fa ciascuna di esse.

Nell'immagine sopra, possiamo vedere che il vettore X rosso è rappresentato dalla prima colonna della matrice, e similmente il vettore Y verde è rappresentato dalla seconda colonna. Una modifica alle colonne modificherà questi vettori. Vedremo come possono essere manipolati nei seguenti esempi.

Non dovresti preoccuparti di manipolare direttamente le righe, poiché solitamente lavoriamo con le colonne. Tuttavia, puoi pensare alle righe della matrice come a un modo per mostrare quali vettori contribuiscono allo spostamento in una determinata direzione.

Quando ci riferiamo a un valore come t.x.y, si tratta della componente Y del vettore colonna X. In altre parole, la parte in basso a sinistra della matrice. Allo stesso modo, t.x.x è la parte in alto a sinistra, t.y.x è la parte in alto a destra e t.y.y è la parte in basso a destra, dove t è la Transform2D.

Scala della matrice di trasformazione

L'applicazione di una scala è una delle operazioni più semplici da capire. Cominciamo posizionando il logo di Godot sotto i nostri vettori in modo da poterne vedere gli effetti su un oggetto:

../../_images/identity-godot.png

Allora, per cambiare la scala della matrice, tutto ciò che dobbiamo fare è moltiplicare ogni componente per la scala desiderata. Aumentiamo la scala di 2. 1 per 2 diventa 2, e 0 per 2 diventa 0, quindi otteniamo questo:

../../_images/scale.png

Per fare ciò in codice, moltiplichiamo ciascuno dei vettori:

var t = Transform2D()
# Scale
t.x *= 2
t.y *= 2
transform = t # Change the node's transform to what we calculated.

Se volessimo riportarla alla sua scala originale, possiamo moltiplicare ogni componente per 0,5. Questo è praticamente tutto ciò che serve per cambiare la scala di una matrice di trasformazione.

Per calcolare la scala dell'oggetto da una matrice di trasformazione esistente, puoi utilizzare length() su ciascuno dei vettori colonna.

Nota

Nei progetti veri e propri, puoi utilizzare il metodo scaled() per cambiare la scala.

Rotazione della matrice di trasformazione

Cominceremo nella stessa maniera di prima, con il logo di Godot sotto la matrice di identità:

../../_images/identity-godot.png

Ad esempio, supponiamo di voler ruotare il nostro logo di Godot di 90 gradi in senso orario. Adesso l'asse X punta verso la destra e l'asse Y punta verso il basso. Se li ruotiamo mentalmente, logicamente vedremmo che il nuovo asse X dovrebbe puntare verso il basso e il nuovo asse Y dovrebbe puntare verso sinistra.

Immagina di afferrare sia il logo di Godot sia i suoi vettori e di farli ruotare attorno al centro. Ovunque finisca la rotazione, l'orientamento dei vettori determina la matrice risultante.

Dobbiamo rappresentare "giù" e "sinistra" in coordinate normali, quindi imposteremo X a (0, 1) e Y a (-1, 0). Questi sono anche i valori di Vector2.DOWN e Vector2.LEFT. Facendo così otteniamo il risultato desiderato, ovvero la rotazione dell'oggetto:

../../_images/rotate1.png

Se hai difficoltà a capire quanto sopra, prova questo esercizio: taglia un quadrato di carta, disegnaci sopra i vettori X e Y, mettilo su un foglio a quadretti, poi ruotalo e annota i punti finali.

Per effettuare una rotazione in codice, dobbiamo poter calcolare i valori da codice. Questa immagine mostra le formule necessarie per calcolare la matrice di trasformazione da un angolo di rotazione. Non preoccuparti se questa parte ti sembra complicata, promettiamo che è la cosa più difficile da sapere.

../../_images/rotate2.png

Nota

Godot rappresenta tutte le rotazioni in radianti, non in gradi. Un giro completo è TAU o pi*2 radianti, e un quarto di giro di 90 gradi è TAU/4 o pi/2 radianti. Lavorare con TAU di solito risulta in codice più leggibile.

Nota

Curiosità: oltre al fatto che Y è giù in Godot, la rotazione è rappresentata in senso orario. Ciò significa che tutte le funzioni matematiche e trigonometriche si comportano allo stesso modo di un sistema con Y in senso antiorario, poiché queste differenze si "annullano". In entrambi i sistemi si può pensare che le rotazioni siano "da X a Y".

Per effettuare una rotazione di 0,5 radianti (circa 28,65 gradi), inseriamo un valore di 0,5 nella formula precedente e valutiamo quali dovrebbero essere i valori effettivi:

../../_images/rotate3.png

Ecco come ciò si farebbe in codice (allegando lo script in un Node2D):

var rot = 0.5 # The rotation to apply.
var t = Transform2D()
t.x.x = cos(rot)
t.y.y = cos(rot)
t.x.y = sin(rot)
t.y.x = -sin(rot)
transform = t # Change the node's transform to what we calculated.

Per calcolare la rotazione dell'oggetto da una matrice di trasformazione esistente, puoi utilizzare atan2(t.x.y, t.x.x), dove t è la Transform2D.

Nota

Nei progetti veri e propri, puoi utilizzare il metodo rotated() per effettuare rotazioni.

Base della matrice di trasformazione

Finora abbiamo lavorato solo con i vettori x e y, che rappresentano la rotazione, la scala e/o l'inclinazione (approfondimento, trattato alla fine). I vettori X e Y sono chiamati insieme la base della matrice di trasformazione. I termini "base" e "vettori di base" sono importanti da conoscere.

Potresti aver notato che Transform2D in realtà ha tre valori di tipo Vector2: x, y e origin. Il valore origin non fa parte della base, ma fa parte della trasformazione e ci serve per rappresentare la posizione. D'ora in poi terremo traccia del vettore origine in tutti gli esempi. Puoi considerare origin come un'altra colonna, ma spesso è meglio considerarla completamente separata.

Nota che in 3D, Godot ha una struttura Basis separata per contenere i tre valori Vector3 della base, poiché il codice può diventare complesso e ha senso separarla da Transform3D (che è composto da una Basis e da un Vector3 aggiuntivo per l'origine).

Traslazione della matrice di trasformazione

La modifica del vettore origine si chiama traslazione della matrice di trasformazione. La traslazione è praticamente un termine tecnico per "spostare" l'oggetto, ma esplicitamente non prevede alcuna rotazione.

Facciamo un esempio per capire meglio questo concetto. Cominceremo con la trasformazione di identità come prima, solo che questa volta terremo traccia del vettore origine.

../../_images/identity-origin.png

Se vogliamo spostare l'oggetto nella posizione (1, 2), dobbiamo impostare il suo vettore origin su (1, 2):

../../_images/translate.png

Esiste inoltre il metodo translated_local(), che esegue un'operazione diversa dall'aggiunta o dalla modifica diretta di origin. Il metodo translated_local() trasla l'oggetto relativamente alla propria rotazione. Ad esempio, un oggetto ruotato di 90 gradi in senso orario si sposterà verso destra quando si chiama il metodo translated_local() con Vector2.UP. Per traslare relativamente al frame globale/padre, utilizza invece translated().

Nota

Il 2D di Godot utilizza coordinate basate sui pixel, quindi nei progetti reali vorrai probabilmente traslare per centinaia di unità.

Mettere tutto insieme

Applicheremo tutto ciò che abbiamo menzionato finora a una sola trasformazione. Per proseguire, crea un progetto con un nodo Sprite2D e usa il logo di Godot come risorsa di texture.

Impostiamo la traslazione a (350, 150), una rotazione di -0,5 rad e una scala di 3. Abbiamo pubblicato uno screenshot e il codice per riprodurlo, ma ti incoraggio a provare a riprodurre lo screenshot senza guardare il codice!

../../_images/putting-all-together.png
var t = Transform2D()
# Translation
t.origin = Vector2(350, 150)
# Rotation
var rot = -0.5 # The rotation to apply.
t.x.x = cos(rot)
t.y.y = cos(rot)
t.x.y = sin(rot)
t.y.x = -sin(rot)
# Scale
t.x *= 3
t.y *= 3
transform = t # Change the node's transform to what we calculated.

Inclinazione della matrice di trasformazione (avanzato)

Nota

Se stai cercando solo come utilizzare le matrici di trasformazione, puoi tranquillamente saltare questa sezione del tutorial. Questa sezione esplora un aspetto poco comune delle matrici di trasformazione, allo scopo di comprenderle meglio.

Node2D fornisce una proprietà di inclinazione pronta all'uso.

Potresti aver notato che una trasformazione ha più gradi di libertà in confronto alle azioni menzionate sopra. La base di una matrice di trasformazione 2D ha quattro numeri totali in due valori di tipo Vector2, mentre un valore di rotazione e un Vector2 per la scala hanno solo 3 numeri. Il concetto di alto livello per il grado di libertà mancante è chiamato inclinazione.

Normalmente, i vettori di base saranno sempre perpendicolari tra loro. Tuttavia, la distorsione può essere utile in alcune situazioni, e comprenderla aiuta a comprendere come funzionano le trasformazioni.

Per mostrare visivamente come apparirà, sovrapponiamo una griglia al logo di Godot:

../../_images/identity-grid.png

Ogni punto su questa griglia si ottiene sommando i vettori di base. L'angolo inferiore destro è X + Y, mentre l'angolo superiore destro è X - Y. Se modifichiamo i vettori di base, l'intera griglia si sposta con essa, poiché è composta dai vettori di base. Tutte le linee sulla griglia che sono attualmente parallele rimarranno parallele a prescindere dalle modifiche apportate ai vettori di base.

Ad esempio, impostiamo Y su (1, 1):

../../_images/shear.png
var t = Transform2D()
# Shear by setting Y to (1, 1)
t.y = Vector2.ONE
transform = t # Change the node's transform to what we calculated.

Nota

Non è possibile impostare i valori grezzi di un Transform2D nell'editor, quindi devi utilizzare codice se vuoi inclinare l'oggetto.

Poiché i vettori non sono più perpendicolari, l'oggetto è stato inclinato. Il centro inferiore della griglia, che è (0, 1) relativo a se stesso, si trova ora nella posizione globale di (1, 1).

Le coordinate intra-oggetto sono chiamate coordinate UV nelle texture, quindi prendiamo in prestito questa terminologia per il nostro caso. Per trovare la posizione globale da una posizione relativa, la formula è U * X + V * Y, dove U e V sono numeri e X e Y sono i vettori di base.

L'angolo inferiore destro della griglia, che si trova sempre nella posizione UV di (1, 1), si trova nella posizione globale di (2, 1), che si calcola da X*1 + Y*1, che è (1, 0) + (1, 1), o (1 + 1, 0 + 1), o (2, 1). Questo corrisponde alla nostra osservazione su dove si trova l'angolo inferiore destro dell'immagine.

Similmente, l'angolo superiore destro della griglia, che si trova sempre nella posizione UV di (1, -1), si trova nella posizione globale di (0, -1), calcolata da X*1 + Y*-1, che è (1, 0) - (1, 1), o (1 - 1, 0 - 1), o (0, -1). Questo corrisponde alla nostra osservazione su dove si trova l'angolo superiore destro dell'immagine.

Speriamo che ora tu abbia compreso appieno come una matrice di trasformazione influisce sull'oggetto e la relazione tra i vettori di base , nonché come manipolare la posizione globale delle "UV" o "intra-coordinate" dell'oggetto.

Nota

In Godot, tutte la trasformazioni matematiche sono eseguite relativamente al nodo padre. Quando parliamo di "posizione globale", sarebbe invece relativa al padre del nodo, se il nodo ne avesse uno.

Se desideri ulteriori spiegazioni, dovresti guardare l'eccellente video di 3Blue1Brown sulle trasformazioni lineari: https://www.youtube.com/watch?v=kYB8IZa5AuE

Applicazioni pratiche delle trasformazioni

Nei progetti veri e propri, solitamente lavorerai con trasformazioni all'interno di trasformazioni tramite più nodi Node2D o Node3D associati tra loro.

Tuttavia, è utile capire come calcolare manualmente i valori di cui abbiamo bisogno. Vedremo come utilizzare Transform2D o Transform3D per calcolare manualmente le trasformazioni dei nodi.

Convertire le posizioni tra le trasformazioni

Ci sono molte situazioni in cui potresti voler convertire una posizione in una trasformazione. Ad esempio, se hai una posizione relativa al giocatore e vorresti trovare la posizione globale (relativa al padre), oppure se hai una posizione globale e vorresti sapere dove si trova relativa al giocatore.

Possiamo ricavare come sarebbe definito un vettore relativo al giocatore nello spazio globale utilizzando l'operatore *:

# World space vector 100 units below the player.
print(transform * Vector2(0, 100))

E possiamo usare l'operatore * nell'ordine opposto per ricavare quale sarebbe la posizione nello spazio globale se fosse definita relativa al giocatore:

# Where is (0, 100) relative to the player?
print(Vector2(0, 100) * transform)

Nota

Se sai in anticipo che la trasformazione è posizionata in (0, 0), puoi invece utilizzare i metodi "basis_xform" o "basis_xform_inv", che evitano di gestire la traslazione.

Muovere un oggetto relativamente a se stesso

Un'operazione comune, soprattutto nei giochi 3D, è quella di muovere un oggetto rispetto a se stesso. Ad esempio, nei giochi sparatutto in prima persona, si vorrebbe che il personaggio si muovesse in avanti (asse -Z) quando si preme W.

Poiché i vettori di base sono l'orientamento relativo al padre e il vettore origine è la posizione relativa al padre, possiamo sommare multipli dei vettori di base per muovere un oggetto relativamente a se stesso.

Questo codice muove un oggetto di 100 unità verso la propria destra:

transform.origin += transform.x * 100

Per muoversi in 3D, dovresti sostituire "x" con "basis.x".

Nota

Nei progetti veri e propri, puoi utilizzare translate_object_local in 3D oppure move_local_x e move_local_y in 2D per fare ciò.

Applicare trasformazioni sulle trasformazioni

Una delle cose più importanti da sapere sulle trasformazioni è come utilizzarne diverse insieme. La trasformazione di un nodo padre influenza tutti i suoi figli. Dissezioniamo un esempio.

In questa immagine, il nodo figlio ha un "2" dopo i nomi dei componenti per distinguerli dal nodo padre. Potrebbe sembrare un po' opprimente con così tanti numeri, ma ricorda che ogni numero è visualizzato due volte (accanto alle frecce e anche nelle matrici) e che quasi la metà dei numeri è zero.

../../_images/apply.png

Le uniche trasformazioni che avvengono qui sono che al nodo padre è stata assegnata una scala di (2, 1), al figlio è stata assegnata una scala di (0,5, 0,5) e a entrambi i nodi sono state assegnate posizioni.

Tutte le trasformazioni del figlio sono influenzate dalle trasformazioni del padre. Il figlio ha una scala di (0,5, 0,5), quindi ci aspetteremmo che sia un quadrato con rapporto 1:1, e lo è, ma solo in relazione al padre. Il vettore X del figlio risulta essere (1, 0) nello spazio globale, perché è scalato dai vettori di base del padre. Similmente, il vettore origin del nodo figlio è impostato a (1, 1), ma questo effettivamente lo sposta di (2, 1) nello spazio globale, a causa dei vettori di base del nodo padre.

Per calcolare manualmente la trasformazione dello spazio mondiale di una trasformazione figlia, questo è il codice che useremmo:

# Set up transforms like in the image, except make positions be 100 times bigger.
var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200))
var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100))

# Calculate the child's world space transform
# origin = (2, 0) * 100 + (0, 1) * 100 + (100, 200)
var origin = parent.x * child.origin.x + parent.y * child.origin.y + parent.origin
# basis_x = (2, 0) * 0.5 + (0, 1) * 0
var basis_x = parent.x * child.x.x + parent.y * child.x.y
# basis_y = (2, 0) * 0 + (0, 1) * 0.5
var basis_y = parent.x * child.y.x + parent.y * child.y.y

# Change the node's transform to what we calculated.
transform = Transform2D(basis_x, basis_y, origin)

Nei progetti veri e propri, possiamo ricavare la trasformazione mondiale del figlio applicando una trasformazione a un'altra utilizzando l'operatore *:

# Set up transforms like in the image, except make positions be 100 times bigger.
var parent = Transform2D(Vector2(2, 0), Vector2(0, 1), Vector2(100, 200))
var child = Transform2D(Vector2(0.5, 0), Vector2(0, 0.5), Vector2(100, 100))

# Change the node's transform to what would be the child's world transform.
transform = parent * child

Nota

Quando si moltiplicano le matrici, l'ordine è importante! Non confonderle.

Infine, applicare la trasformazione di identità non farà mai nulla.

Se desideri ulteriori spiegazioni, dovresti guardare l'eccellente video di 3Blue1Brown sulla composizione di matrici: https://www.youtube.com/watch?v=XkY2DOUCWMU

Invertire una matrice di trasformazione

La funzione "affine_inverse" restituisce una trasformazione che "annulla" la trasformazione precedente. Questo può essere utile in alcune situazioni. Vediamo qualche esempio.

Moltiplicando una trasformazione inversa per la trasformazione normale si annullano tutte le trasformazioni:

var ti = transform.affine_inverse()
var t = ti * transform
# The transform is the identity transform.

Trasformando una posizione per una trasformazione e la sua inversa si ottiene la stessa posizione:

var ti = transform.affine_inverse()
position = transform * position
position = ti * position
# The position is the same as before.

Come funziona tutto in 3D?

Uno dei cose migliori delle matrici di trasformazione è che funzionano in modo molto simile tra trasformazioni 2D e 3D. Tutto il codice e le formule usati sopra per il 2D funzionano allo stesso modo in 3D, con tre eccezioni: l'aggiunta di un terzo asse, il fatto che ogni asse sia di tipo Vector3 e anche che Godot memorizzi Basis separatamente dal Transform3D, poiché la matematica può diventare complessa e ha senso separarle.

Tutti i concetti relativi a traslazione, rotazione, scala e inclinazione in 3D sono gli stessi del 2D. Per cambiare la scala, prendiamo ogni componente e la moltiplichiamo; per ruotare, modifichiamo la posizione di ciascun vettore di base; per traslare, manipoliamo l'origine; e per inclinare, modifichiamo i vettori di base affinché non siano perpendicolari.

../../_images/3d-identity.png

Se vuoi, è una buona idea sperimentare con le trasformazioni per comprenderne il funzionamento. Godot permette di modificare le matrici di trasformazione 3D direttamente dall'ispettore. Puoi scaricare questo progetto, che include linee e cubi colorati per aiutarti a visualizzare i vettori di Basis e l'origine, sia in 2D sia in 3D: https://github.com/godotengine/godot-demo-projects/tree/master/misc/matrix_transform

Nota

Non è possibile modificare la matrice di trasformazione di Node2D direttamente nell'ispettore di Godot 4.0. Ciò potrebbe cambiare in una futura versione di Godot.

Se desideri ulteriori spiegazioni, dovresti consultare l'eccellente video di 3Blue1Brown sulle trasformazioni lineari in 3D: https://www.youtube.com/watch?v=rHLEWRxRGiM

Rappresentare una rotazione in 3D (avanzato)

La differenza più grande tra le matrici di trasformazione 2D e 3D è come viene rappresentata la rotazione da sola, senza i vettori di base.

In 2D, abbiamo un modo semplice (atan2) per passare da una matrice di trasformazione a un angolo. In 3D, la rotazione è troppo complessa da rappresentare con un singolo numero. Si potrebbero usare gli angoli di Eulero, che possono rappresentare le rotazioni come un insieme di 3 numeri, tuttavia sono limitati e non particolarmente utili, tranne in casi banali.

In 3D, in genere non utilizziamo gli angoli, ma utilizziamo una base di trasformazione (praticamente ovunque in Godot) oppure i quaternioni. Godot può rappresentare i quaternioni tramite la struct Quaternion. Il nostro consiglio è di ignorare completamente il loro funzionamento interno, perché sono molto complicati e poco intuitivi.

Tuttavia, se proprio vuoi sapere come funzionano, ecco alcune ottime risorse, che puoi seguire nell'ordine indicato:

https://www.youtube.com/watch?v=mvmuCPvRoWQ

https://www.youtube.com/watch?v=d4EgbgTm0Bg

https://eater.net/quaternions