Geometria (Vettore Fila Maggiore vs Colonna Maggiore)

Geometria

Prima in questa lezione, abbiamo spiegato che i vettori (o punti) possono essere scritti come matrici (una riga, tre colonne). Notate però che avremmo potuto anche scriverli come matrici (tre righe, una colonna). Tecnicamente, questi due modi di esprimere punti e vettori come matrici sono perfettamente validi e scegliere un modo o l’altro è solo una questione di convenzione.

Vettore scritto come matrice: \V = inizio matrice x & y & z fine matrice)

Vettore scritto come matrice: \Nel primo esempio (matrice) abbiamo espresso il nostro vettore o punto in quello che chiamiamo l’ordine riga-maggiore: il vettore (o punto) è scritto come una riga di tre numeri. Nel secondo esempio, diciamo che i punti o i vettori sono scritti in ordine colonna-maggiore: scriviamo le tre coordinate del vettore o del punto in verticale, come una colonna.

Ricordo che esprimiamo punti e vettori come matrici per moltiplicarli per matrici di trasformazione (per semplicità lavoreremo con invece di matrici). Abbiamo anche imparato che possiamo moltiplicare matrici solo quando il numero di colonne della matrice di sinistra e il numero di righe della matrice di destra sono uguali. In altre parole le matrici e possono essere moltiplicate tra loro ma le matrici e non possono. Notate che se scriviamo un vettore come matrice possiamo moltiplicarlo per una matrice (assumendo che questa matrice sia a destra all’interno della moltiplicazione), ma se scriviamo questo vettore come matrice allora non possiamo moltiplicarlo per una matrice. Questo è illustrato nei seguenti esempi. Le dimensioni interne (3 e 3) delle matrici coinvolte nella moltiplicazione sono le stesse (in verde) quindi questa moltiplicazione è valida (e il risultato è un punto trasformato scritto in forma di matrice):

$$* = \begin{bmatrix}x & y & z\end{bmatrix} * \begin{bmatrix}c_{00}&c_{01}&{c_{02}}\\c_{10}&c_{11}&{c_{12}}\\c_{20}&c_{21}&{c_{22}}\\\end{bmatrix} =\begin{bmatrix}x’&y’&z’\end{bmatrix}$$

The dimensioni interne (1 e 3) delle matrici coinvolte nella moltiplicazione non sono le stesse (in rosso) quindi questa moltiplicazione non è possibile:

$$* \freccia \matrice iniziale \matrice finale \matrice finale \matrice finale * \begin{bmatrix} c_{00}&c_{01}&{c_02}} c_{10}&c_{11}&{c_12}} c_{20}&c_21}&{c_22}} \end{bmatrix}$$

E allora cosa facciamo? La soluzione a questo problema non è moltiplicare il vettore o il punto per la matrice, ma la matrice M per il vettore V. In altre parole, spostiamo il punto o il vettore a destra all’interno della moltiplicazione:

$$* \rightarrow \begin{bmatrix} c_{00}&c_{01}&{c_{02}} c_{10}&c_{11}&{c_12}} c_{20}&c_21}&{c_22}} \end{bmatrix} * \begin{bmatrix}x\y\z\end{bmatrix} = \begin{bmatrix}x’\y’\z’\end{bmatrix}$$

Nota che il risultato di questa operazione è un punto trasformato scritto nella forma di una matrice. Quindi abbiamo un punto da cui partire e finiamo con un punto trasformato che è quello che vogliamo. Problema risolto. Per riassumere, quando per convenzione decidiamo di esprimere vettori o punti in ordine di riga maggiore (), dobbiamo mettere il punto a sinistra della moltiplicazione e il punto a destra all’interno del segno di moltiplicazione. Questo si chiama in matematica, una pre-moltiplicazione o sinistra. Se invece decidete di scrivere i vettori in ordine colonna-maggiore (), la matrice deve stare a sinistra della moltiplicazione e il vettore o il punto a destra. Questa è chiamata una moltiplicazione a destra o post-moltiplicazione.

Dobbiamo stare attenti a come questi termini sono effettivamente usati. Per esempio la documentazione di Maya dice “le matrici sono post-moltiplicate in Maya. Per esempio, per trasformare un punto P dallo spazio-oggetto allo spazio-mondo (P’) è necessario post-moltiplicare per la matrice mondo. (P’ = P x WM)”, il che confonde perché in realtà è una pre-moltiplicazione ma stanno parlando della posizione della matrice rispetto al punto in questo caso particolare. Questo è in realtà un uso scorretto della terminologia. Si sarebbe dovuto scrivere che in Maya, i punti e i vettori sono espressi come vettori riga-maggiore e che quindi sono pre-moltiplicati (nel senso che il punto o il vettore appare prima della matrice nella moltiplicazione).

La seguente tabella riassume le differenze tra le due convenzioni (dove P, V e M stanno rispettivamente per Punto, Vettore e Matrice).

Ordine di riga maggiore

\(P/V = inizio{matrice}x & y & z{matrice}\fine)

Sinistra o premoltiplicazione

P/V * M

Colonna-ordine maggiore

(P/V = inizio matrice x \ y \ z fine matrice)

Destra o postmoltiplicazione

M * P/V

Ora che abbiamo imparato queste due convenzioni potreste chiedere “non si tratta solo di scrivere cose sulla carta?”. Sappiamo come calcolare il prodotto di due matrici A e B: moltiplicare ogni coefficiente nella riga corrente di A per gli elementi associati nella colonna corrente di B e sommare il risultato. Applichiamo questa formula usando le due convenzioni e confrontiamo i risultati:

Ordine di riga maggiore

$${ \begin{bmatrice}x & y & z\fine{bmatrice} * \begin{bmatrix}a & b & c \d & e & f \g & h & i\end{bmatrix} }$$

$${ \begin{array}{l}x’ = x * a + y * d + z * g\y’ = x * b + y * e + z * h\z’ = x * c + y * f + z * i\end{array} }$$

Ordine maggiore della colonna

$${ \begin{bmatrice} a & b & c \d & e & f \g & h & i \end{bmatrice} * \begin{bmatrix}x\\y\\z\end{bmatrix} * $$

$${\begin{array}{l}x’ = a * x + b * y + c * z\y’ = d * x + e * y + f * z\z’ = g * x + h * y + i * z\end{array} }$$

Moltiplicare un punto o un vettore per una matrice dovrebbe darci lo stesso risultato sia che usiamo un ordine maggiore di riga o di colonna. Se usate un’applicazione 3D per ruotare un punto di un certo angolo intorno all’asse z, vi aspettate che il punto sia in una certa posizione dopo la rotazione, non importa quale convenzione interna lo sviluppatore abbia usato per rappresentare punti e vettori. Tuttavia, come potete vedere guardando la tabella qui sopra, moltiplicando un punto (o vettore) riga-maggiore e colonna-maggiore per la stessa matrice, chiaramente non avremmo lo stesso risultato. Per tornare sui nostri passi, avremmo effettivamente bisogno di trasporre la matrice usata nella moltiplicazione colonna-maggiore per essere sicuri che x’, y’ e z’ siano uguali (se avete bisogno di ricordare cos’è la trasposizione di una matrice, controllate il capitolo sulle Operazioni sulle matrici). Ecco cosa otteniamo:

Row-major order

$${ \begin{bmatrix}x & y & z\end{bmatrix} * \begin{bmatrix}a & b & c \d & e & f \g & h & i\end{bmatrix} }$$

$${\begin{array}{l}x’ = x * a + y * d + z * g\y’ = x * b + y * e + z * h\z’ = x * c + y * f + z * i\end{array} }$$

Ordine maggiore della colonna

$$${ \begin{bmatrice} a & d & g \b & e & h \c & f & i\end{bmatrice} * \begin{bmatrix}x\\y\\z\end{bmatrix} * $$

$${\begin{array}{l}x’ = a * x + d * y + g * z\y’ = b * x + e * y + h * z\z’ = c * x + f * y + i * z\end{array} }$$

In conclusione, passare dall’ordine riga-maggiore all’ordine colonna-maggiore comporta non solo lo scambio del punto o vettore e della matrice nella moltiplicazione ma anche la trasposizione della matrice, per garantire che entrambe le convenzioni diano lo stesso risultato (e viceversa).

Da queste osservazioni, possiamo vedere che qualsiasi serie di trasformazioni applicate a un punto o a un vettore quando si usa una convenzione riga-maggiore può essere scritta in ordine sequenziale (o di lettura). Immaginate per esempio di voler traslare il punto P con la matrice T, poi ruotarlo attorno all’asse z con Rz e poi attorno all’asse y con Ry. Potete scrivere:

$$P’=P * T * R_z * R_y$$

Se doveste usare una notazione colonna-maggiore dovreste chiamare la trasformazione in ordine inverso (cosa che si potrebbe trovare controintuitiva):

$P’=R_y * R_z * T * P$$

Potreste quindi pensare: “ci deve essere una ragione per preferire un sistema all’altro”. In realtà, entrambe le convenzioni sono corrette e ci danno lo stesso risultato, ma per alcune ragioni tecniche, i testi di matematica e fisica trattano generalmente i vettori come vettori colonna.

L’ordine di trasformazione quando usiamo matrici colum-major è più simile in matematica al modo in cui scriviamo la valutazione e la composizione delle funzioni.

La convenzione di matrice row-major comunque rende le matrici più facili da insegnare ed è il motivo per cui la usiamo per Scratchapixel (così come Maya, DirectX. Sono anche definite come standard nelle specifiche di RenderMan). Tuttavia alcune API 3D, come OpenGL, usano una convenzione colonna-maggiore.

Implicazione nella codifica: Ha un impatto sulle prestazioni?

C’è un altro aspetto potenzialmente molto importante da prendere in considerazione se si deve scegliere tra row-major e column-major, ma questo non ha nulla a che fare con le convenzioni stesse e quanto sia pratico uno rispetto all’altro. Ha più a che fare con il computer e il modo in cui funziona. Ricordate che avremo a che fare con le matrici. Tipicamente l’implementazione di una matrice in C++ assomiglia a questa:

class Matrix44{ … float m;};

Come potete vedere i 16 coefficienti della matrice sono memorizzati in un array bidimensionale di float (o doppi a seconda della precisione che vi serve. La nostra classe C++ Matrix è un template). Il che significa che in memoria i 16 coefficienti saranno disposti nel seguente modo: c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33. In altre parole, sono disposti in modo contiguo nella memoria. Vediamo ora come si accede a questi coefficienti in una moltiplicazione vettoriale-matrice dove i vettori sono scritti in ordine di riga maggiore:

// row-major orderx’ = x * c00 + y * c10 + z * c20y’ = x * c01 + y * c11 + z * c21z’ = x * c02 + y * c12 + z * c22

Come potete vedere gli elementi della matrice per x’ non sono accessibili in sequenza. In altre parole, per calcolare x’ abbiamo bisogno del 1°, 5° e 9° float della matrice 16 floats. Per calcolare y’ abbiamo bisogno di accedere al 2°, 6° e 10° float di questa matrice. E infine per z’ abbiamo bisogno del 3°, 7° e 11° float della matrice. Nel mondo dell’informatica, accedere agli elementi di un array in un ordine non sequenziale, non è necessariamente una buona cosa. In realtà potenzialmente degrada le prestazioni della cache della CPU. Non entreremo troppo nei dettagli qui, ma diciamo solo che la memoria più vicina a cui la CPU può accedere è chiamata cache. Questa cache è molto veloce da raggiungere, ma può memorizzare solo un numero molto limitato di dati. Quando la CPU ha bisogno di accedere a dei dati, prima controlla se esistono nella cache. Se c’è, la CPU accede subito a questi dati (cache hit), ma non c’è (cache miss), deve prima creare una voce nella cache per esso, poi copiare in questa posizione i dati dalla memoria principale. Questo processo è ovviamente più dispendioso in termini di tempo rispetto a quando i dati esistono già nella cache, quindi idealmente vogliamo evitare il più possibile i cache miss. Oltre a copiare i dati particolari dalla memoria principale, la CPU copia anche una parte dei dati che si trovano proprio accanto (per esempio i 24 byte successivi), perché gli ingegneri hardware hanno capito che se il vostro codice ha bisogno di accedere a un elemento di un array, per esempio, è probabile che acceda agli elementi che lo seguono subito dopo. In effetti, nei programmi, spesso facciamo un loop sugli elementi di un array in ordine sequenziale e questa assunzione è quindi probabile che sia vera. Applicato al nostro problema della matrice, accedere ai coefficienti della matrice in ordine non sequenziale può quindi essere un problema. Supponendo che la CPU carichi il float richiesto nella cache più i 3 float successivi, la nostra attuale implementazione potrebbe portare a molte mancanze nella cache, poiché i coefficienti usati per calcolare x’ y’ e z’ sono distanti 5 float nell’array. D’altra parte, se si usa una notazione di ordine colonna-maggiore, il calcolo di x’ per esempio richiede di accedere al 1°, 2° e 3° elemento della matrice.

// column-major orderx’ = c00 * x + c01 * y + c02 * zy’ = c10 * x + c11 * y + c12 * zz’ = c20 * x + c21 * y + c22 * z

I coefficienti sono accessibili in ordine sequenziale, il che significa anche che facciamo un buon uso del meccanismo della cache della CPU (solo 3 cache misses invece di 9 nel nostro esempio). In conclusione possiamo dire che da un punto di vista di programmazione, implementare la nostra moltiplicazione punto o vettore-matrice usando una convenzione di ordine colum-major potrebbe essere migliore, in termini di prestazioni, della versione che usa la convenzione di ordine row-major. In pratica, però, non siamo stati in grado di dimostrare che questo fosse effettivamente il caso (quando compilate il vostro programma usando i flag di ottimizzazione -O, -O2 o -O3, il compilatore può fare il lavoro per voi ottimizzando i cicli sugli array multidimensionali) e abbiamo usato con successo la versione con ordine maggiore di riga senza alcuna perdita di prestazioni rispetto a una versione dello stesso codice che usa un’implementazione con ordine maggiore di colonna.

template<typename T>class Vec3{public: Vec3(T xx, T yy, T zz) : x(xx), y(yy), z(zz) {} T x, y, z, w;}; template<typename T>class Matrix44{public: T m; Vec3<T> multVecMatrix(const Vec3<T> &v) {#ifdef ROWMAJOR return Vec3<T>( v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m);#else return Vec3<T>( v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m, v.x * m + v.y * m + v.z * m);#endif }}; #include <cmath>#include <cstdlib>#include <cstdio>#include <csttime> #define MAX_ITER 10e8 int main(int argc, char **argv){ clock_t start = clock(); Vec3<float> v(1, 2, 3); Matrix44<float> M; float *tmp = &M.m; for (int i = 0; i < 16; i++) *(tmp + i) = drand48(); for (int i = 0; i < MAX_ITER; ++i) { Vec3<float> vt = M.multVecMatrix(v); } fprintf(stderr, “Clock time %f\n”, (clock() – start) / float(CLOCKS_PER_SEC)); return 0;}

Ordine riga-maggiore e colonna-maggiore nel calcolo

Per amore di completezza, menzioniamo anche che i termini ordine riga-maggiore e colonna-maggiore possono anche essere usati nel calcolo per descrivere il modo in cui gli elementi degli array multidimensionali sono disposti in memoria. In ordine fila-maggiore, gli elementi di una matrice multidimensionale sono disposti uno dopo l’altro, da sinistra a destra, dall’alto in basso. Questo è il metodo usato da C/C++. Per esempio la matrice:

$$M = \begin{bmatrix}1&2&3\4&5&6\end{bmatrix}$$

potrebbe essere scritta in C/C++ come:

float m={1, 2, 3}, {4, 5, 6}};

e gli elementi di questo array sarebbero disposti in modo contiguo nella memoria lineare come:

1 2 3 4 5 6

Nell’ordine colonna-maggiore, che è usato da linguaggi come FORTRAN e MATLAB, gli elementi della matrice sono memorizzati dall’alto in basso, da sinistra a destra. Usando lo stesso esempio di matrice, gli elementi della matrice sarebbero immagazzinati (e accessibili) in memoria nel modo seguente:

1 4 2 5 3 6

Conoscere come gli elementi di una matrice sono disposti in memoria è importante soprattutto quando si cerca di accedervi usando l’offset del puntatore e l’ottimizzazione del ciclo for (abbiamo spiegato precedentemente in questo capitolo che potrebbe influenzare le prestazioni della cache della CPU). Tuttavia, poiché considereremo solo C/C++ come nostro linguaggio di programmazione, l’ordinamento column-major (applicato all’informatica) non è di grande interesse per noi. Stiamo solo menzionando il significato dei termini in informatica, in modo che siate consapevoli che potrebbero descrivere due cose diverse a seconda del contesto in cui vengono usati. Dovreste fare attenzione a non confonderli. Nel contesto della matematica, descrivono se si trattano i vettori (o punti) come righe di coordinate o come colonne e la seconda, e nel contesto dell’informatica, descrivono il modo in cui un certo linguaggio di programmazione memorizza e accede agli elementi di array multidimensionale (che sono le matrici) in memoria.

OpenGL è un caso interessante a questo proposito. Quando GL fu creato inizialmente, gli sviluppatori scelsero la convenzione vettoriale riga-maggiore. Gli sviluppatori che hanno esteso OpenGL hanno però pensato di tornare al vettore maggiore a colonne, cosa che hanno fatto. Tuttavia, per ragioni di compatibilità, non volevano cambiare il codice per la moltiplicazione punto-matrice e decisero invece di cambiare l’ordine in cui i coefficienti della matrice erano memorizzati nella memoria. In altre parole OpenGL memorizza i coefficienti in ordine colonna-maggiore, il che significa che i coefficienti di traslazione m03, m13 e m23 da una matrice che usa il vettore colonna-maggiore hanno gli indici 13, 14, 15 nella matrice float, così come i coefficienti di traslazione m30, m31 e m32 da una matrice che usa il vettore riga-maggiore.

Sommario

Le differenze tra le due convenzioni sono riassunte nella tabella seguente:

Vettore riga-maggiore (Matematica) Colonna-(Matematica)

\(P/V===begin{bmatrix}x & y & z{bmatrix}\)

\(P/V = inizio matrice x \ y \ z fine matrice)

Pre-moltiplicazione \(vM\)

Post-moltiplicazione \(Mv\)

L’ordine di chiamata e l’ordine di applicazione delle trasformazioni è lo stesso: “prendi P, trasforma con T, trasforma con Rz, trasforma con Ry” si scrive come \(P’=P*T*R_z*R_y\)

L’ordine di chiamata è il contrario dell’ordine di applicazione delle trasformazioni: “prendi P, trasforma con T, trasforma con Rz, trasforma con Ry” è scritto come \(P’=R_y*R_z*T*P\)

API: Direct X, Maya

API: OpenGL, PBRT, Blender

Le righe della matrice rappresentano le basi (o assi) di un sistema di coordinate (rosso: asse x, verde: asse y, blu:asse z)

$${\begin{bmatrice} \color{red}{c_{00}}& \color{red}{c_{01}}&\color{red}{c_{02}}&0\\ \color{green}{c_{10}}& \color{green}{c_{11}}&\color{green}{c_{12}}&0\\ \color{blue}{c_{20}}& \color{blue}{c_{21}}&\color{blue}{c_{22}}&0\\0&0&0&1 \end{bmatrix} } $$

Le colonne della matrice rappresentano le basi (o assi) di un sistema di coordinate (rosso: asse x, verde: asse y, blu:asse z)

$${ \begin{bmatrice} \color{red}{c_{00}}& \color{green}{c_{01}}&\color{blue}{c_{02}}&0\\ \color{red}{c_{10}}& \color{green}{c_{11}}&\color{blue}{c_{12}}&0\\ \color{red}{c_{20}}& \color{green}{c_{21}}&\color{blue}{c_{22}}&0\\0&0&0&1\end{bmatrix} }$$

I valori di traduzione sono memorizzati negli elementi c30, c31 e c32.

$${\begin{bmatrix}1&0&0&0\\0&1&0&0\\0&0&1&0\\Tx&Ty&Tz&1\end{bmatrix} }$$

I valori di traduzione sono memorizzati negli elementi c03, c13 e c23.

$${begin{bmatrix}1&0&0&Tx\0&1&0&Ty\0&0&1&Tz\0&0&0&0&1\end{bmatrix} }$$

Trasporre la matrice per usarla come una matrice ordinata colonna-maggiore

Trasporre la matrice per usarla come una matrice ordinata riga-major ordered matrix

Matrice maggiore di riga (Computing) Matrice maggiore di colonna (Computing)

API: Direct X, Maya, PBRT

API: OpenGL

Un lettore ha postato una domanda su Stackoverflow suggerendo che la tabella sopra è confusa. L’argomento è confuso e nonostante il nostro miglior tentativo di fare luce sulla questione, molte persone sono ancora confuse. Abbiamo pensato che la nostra risposta su Stackoverflow potrebbe sperare di portare un’altra visione sulla questione.

Hai la teoria (ciò che fai in matematica con carta e penna) e ciò che fai con la tua implementazione (C++). Sono due problemi diversi.

Matematica: si possono usare due notazioni, o colonna o riga maggiore. Con il vettore maggiore di riga, sulla carta, devi scrivere la moltiplicazione vettore-matrice vM dove v è il vettore di riga (1×4) e M la tua matrice 4×4. Perché? Perché matematicamente puoi scrivere solo *, e non il contrario. Allo stesso modo, se usi la colonna, allora il vettore deve essere scritto verticalmente, o in notazione (4 righe, 1 colonna). Così, la moltiplicazione con una matrice può essere scritta solo come segue: . Si noti che la matrice viene messa davanti al vettore: Mv. La prima notazione si chiama moltiplicazione a sinistra o pre-moltiplicazione (perché il vettore è a sinistra del prodotto) e la seconda (Mv) si chiama moltiplicazione a destra o post-moltiplicazione (perché il vettore è a destra del prodotto). Come vedi i termini derivano dal fatto che il vettore si trovi sul lato sinistro (davanti, o “pre”) o sul lato destro (dopo, o “post”) della matrice.

Ora, se devi trasformare un vettore (o un punto) allora devi fare attenzione all’ordine di moltiplicazione, quando li scrivi sulla carta. Se vuoi traslare qualcosa con la matrice T e poi ruotare con R e poi scalare con S, allora in un mondo a colonne maggiori, devi scrivere v’ = S * R * T * v. In un mondo a righe maggiori devi scrivere v’ = v * T * R * S.

Questo è per la teoria. Chiamiamola la convenzione vettoriale riga/colonna.

Computer: poi arriva il punto in cui si decide di implementare questo in C++ diciamo. La cosa buona è che il C++ non ti impone niente di niente. Puoi mappare i valori dei coefficienti della tua matrice in memoria nel modo che vuoi, e puoi scrivere il codice per eseguire una moltiplicazione di matrice per un’altra matrice nel modo che vuoi. Allo stesso modo, il modo in cui accedete ai coefficienti per una moltiplicazione matrice-vettore dipende completamente da voi; dovete fare una chiara distinzione tra come mappate i vostri coefficienti in memoria e quali convenzioni dovete usare da un punto di vista matematico per rappresentare i vostri vettori. Questi sono due problemi indipendenti. Chiamiamo questa parte la disposizione riga/colonna maggiore.

Per esempio potete dichiarare una classe matrice come un array di 16 float contigui. Questo va bene. Dove i coefficienti m14, m24, m34 rappresentano la parte di traslazione della matrice (Tx, Ty, Tz), quindi si assume che la vostra “convenzione” sia row-major anche se vi viene detto di usare la convenzione della matrice OpenGL che si dice sia column-major. Qui la possibile confusione deriva dal fatto che la mappatura dei coefficienti in memoria è diversa dalla rappresentazione mentale che vi state facendo di una matrice “column-major”. Tu codifichi “riga” ma ti è stato detto di usare (da un punto di vista matematico) “colonna”, da qui la tua difficoltà a dare un senso se fai le cose bene o male.

L’importante è vedere una matrice come una rappresentazione di un sistema di coordinate definito da tre assi, e una traslazione. Dove e come immagazzinate questi dati in memoria dipende completamente da voi. Supponendo che i tre vettori che rappresentano i tre assi del sistema di coordinate si chiamino AX(x,y,z), AY(x,y,z), AZ(x,y,z), e che il vettore di traslazione sia indicato con (Tx, Ty, Tz), allora matematicamente se usate il vettore colonna avete:

$$M = \begin{bmatrix} AXx & AYx & AZx & Tx\ AXy & AYy & AZy & Ty \ AXz & AYz & AZz & Tz \ 0 & 0 & 1 & 1\end{bmatrix}$$

Gli assi del sistema di coordinate sono scritti in verticale. Ora se hai se usi la riga maggiore:

$$M = \inizio{bmatrice} AXx & AXy & AXz & 0 \AYx & AYy & AYz & 0 \ AZx & AZy & AZz & 0 \ Tx & Ty & Tz & 1 \end{bmatrix}$$

Gli assi del sistema di coordinate sono scritti in orizzontale. Quindi il problema ora, quando si tratta del mondo dei computer, è come memorizzare questi coefficienti nella memoria. Puoi anche fare:

float m = { AXx, AXy, AXz, 0, AYx, AYy, AYz, 0, AZx, AZy, AZz, 0, Tx, Ty, Tz, 1};

Ti dice però quale convenzione usi? No. Puoi anche scrivere:

float m = { AXx, AXy, AXz, Tx, AYx, AYy, AYz, Ty, AZx, AZy, AZz, Tz, 0, 0, 0, 1};

o:

float m = { AXx, AYx, AZx, Tx, AXy, AYy, AZy, Ty, AXz, AYz, AZz, Tz, 0, 0, 0, 1};

Di nuovo, questo non ti dà un’indicazione particolare di quale convenzione “matematica” usi. State semplicemente immagazzinando 16 coefficienti in memoria in modi diversi e questo va perfettamente bene, purché sappiate qual è quel modo, in modo da potervi accedere in modo appropriato in seguito. Ora tenete a mente che un vettore moltiplicato per una matrice dovrebbe darvi lo stesso vettore sia che usiate una notazione matematica a righe o a colonne. Quindi l’importante è moltiplicare le coordinate (x,y,z) del vostro vettore per i coefficienti giusti della matrice, il che richiede la conoscenza di come “voi” avete deciso di memorizzare il coefficiente della matrice in memoria:

Vector3 vecMatMult ( Vector3 v, float AXx, float AXy, float AXz, float Tx, float AYx, float AYy, float AYz, float Ty, float AZz, float AZy, float AZz, float Tz) { return Vector3( v.x * AXx + v.y * AYx + v.z * AZx + Tx, v.x * AXy + v.y * AYy + v.z * AZy + Ty, v.x * AXz + v.y * AZz + v.z * AZz + Tz}

Abbiamo scritto questa funzione per sottolineare il fatto che non importa quale convenzione usiate, il risultato della moltiplicazione vettore * matrice è solo una moltiplicazione e un’addizione tra le coordinate di ingresso del vettore e le coordinate degli assi del sistema di coordinate AX, AY e AZ (indipendentemente dalla notazione che usate, e indipendentemente dal modo in cui le memorizzate). Se usate:

float m = { AXx, AXy, AXz, 0, AYx, AYy, AYz, 0, AZx, AZy, AZz, 0, Tx, Ty, Tz, 1};

dovete chiamare:

vecMatMult(v, m, m, m, m, m, m, m, m, m, …

Se usi:

float m = { AXx, AYx, AZx, Tx, AXy, AYy, AZy, Ty, AXz, AYz, AZz, Tz, 0, 0, 0, 1};

Devi chiamare:

vecMatMult(v, m, m, m, m, m, m, m, m, m, …

Questo ti dice quale convenzione usi? No. Hai solo bisogno di chiamare i coefficienti giusti nei posti giusti quando fai una moltiplicazione vec * mat. E questo è tutto, per quanto sconcertante possa sembrare.Ora le cose sono leggermente diverse quando si tratta di moltiplicazione mat * mat. Si può supporre che l’ordine in cui si moltiplicano le matrici non sia lo stesso. Quindi R * S * T non è lo stesso di T * S * R. L’ordine infatti conta. Ora di nuovo se usi “row major” allora matematicamente devi scrivere:

mt11 = ml11 * mr11 + ml12 * mr21 + ml13 * mr31 + ml14 * mr41

dove ml è la matrice di sinistra e mr quella di destra: mt = ml * mr. Comunque nota che non abbiamo usato parentesi per gli indici di accesso perché non vogliamo suggerire che stiamo accedendo ad elementi memorizzati in un array 1D qui. Stiamo solo parlando dei coefficienti delle matrici come scritto sulla carta. Se volete scrivere questo in C++, allora tutto dipende da come avete memorizzato i vostri coefficienti in memoria come suggerito sopra.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato.