Non importa. Il mio intento adesso è di fornirvi quell'infarinatura generale di concetti matematici (tra i quali elementi di algebra lineare e trigonometria) che servono per entrare un po' nel dettaglio nella spiegazione del funzionamento di un motore di rendering 3D.
Ho visto che diversi si sono cimentati nella programmazione di un sistema di fake 3d (3d via software invece che hardware, come sarebbe giusto), ma nessuno si è mai preoccupato troppo di fare qualcosa di valido.
Il mio obiettivo è di creare passo-passo un engine di rendering via software, magari sia game maker che in C++, giusto per apprezzare la differenza tra due linguaggi di diverso livello di astrazione nel modello delle virtual machine.
Conto di farlo in più "episodi", non penso di riuscire a scrivere tutto oggi.
Non aspettatevi un rendering in real time, immaginatevi piuttosto qualcosa come potrebbe essere il rendering prodotto dal vostro programma di modeling preferito, lento ma completo. Si tratta solo di un esempio per illustrare le vere tecniche per renderizzare grafica 3D, senza supporto gpu non andrà mai tanto veloce.
Propedeuticità per la guida: nessuna. Quanto scrivo vuole anche essere un buon riferimento matematico per il forum.
Non darò niente per scontato: ogni volta che mi servirà qualche nozione importante la spiegherò in spoiler, in modo da permettere a quelli che già sanno di saltarlo. Naturalmente non mi considero un essere superiore, i più esperti sono caldamente invitati a leggere comunque tutto e a controllare per bene che non abbia lasciato qua e la qualche errore.
EDIT: Lascio l'introduzione precedente per correttezza, ma nel corso della realizzazione di questa guida ho dovuto ridimensionare
il mio obiettivo. Non posso assolutamente lavorare con GM, è troppo, troppo lento. Ero preparato ad affrontare un problema del genere, ma non pensavo fosse così grave. Impiegare dei minuti per vedere qualcosa allunga troppo anche i tempi di debugging, oltre ogni limite imposto dalla mia pazienza.
Lavorerò pertanto solo con qualche linguaggio di più basso livello come il C++. Questo significa che oltre a un piccolo exe dimostrativo, in questo tutorial non vedrete codice, visto che non tutti potrebbero comprenderlo.
Utilizzerò diverse volte l'espressione di sommatoria, pertanto se non sapete cos'è cominciate espandendo lo spoiler.
Spoiler
Una sommatoria è un po' come un for in game maker.
Prendiamo tre numeri interi i,n,m tali che n <= m. In questo paragone, i è la variabile che fa da indice del for.
Scrivere
è un po' come scrivere
In altre parole quella sommatoria si espande in una somma dei (m-n)+1 valori:
f(n) + f(n+1) + f(n+2) + ... + f(m-1) + f(m).
Notate come la i in f(i) nell'immagine venga sostituita ogni volta con il suo valore attuale, che viene incrementato dopo ogni espressione.
Prendiamo tre numeri interi i,n,m tali che n <= m. In questo paragone, i è la variabile che fa da indice del for.
Scrivere
è un po' come scrivere
Codice: Seleziona tutto
risultato = 0;
for(i=n;i<=m;i+=1)
risultato += f(i);
f(n) + f(n+1) + f(n+2) + ... + f(m-1) + f(m).
Notate come la i in f(i) nell'immagine venga sostituita ogni volta con il suo valore attuale, che viene incrementato dopo ogni espressione.
Nello spoiler seguente:
- definizione di funzione reale per valori reali, di Dominio e Codominio
- cenni di notazione insiemistica
- gli angoli e i radianti
- esempio di funzione: degtorad
- la funzione seno
- la funzione coseno
- esempi: lengthdir_x,y,z
Spoiler
Prima di tutto la definizione di funzione. Cercherò di esprimermi in termini più familiari per voi programmatori di gml.
Una funzione matematica ha lo stesso significato logico di uno script nel GML. Immaginatevela come una scatola magica che trasforma univocamente l'input che gli viene dato in un determinato output, secondo alcune regole ben specifiche. In particolare utilizzeremo funzioni ad una sola variabile reale (un solo parametro per input, che generalmente viene chiamato x).
Funzioni di questo tipo si indicano in questo modo:
dove f è il nome della funzione. Dopo : viene specificato l'insieme Dominio -> Codominio della funzione. L'insieme Dominio è il raggruppamento di tutti i numeri che la funzione può accettare come input. Se diamo in pasto alla funzione un valore non incluso nel suo dominio in matematica si dice che la funzione non è definita. In programmazione una cosa del genere potrebbe condurre ad un comportamento inaspettato della funzione.
Similmente, il Codominio di una funzione è l'insieme dei valori che la funzione può restituire.
Nel nostro caso sia Dominio che Codominio sono , a significare che input e output possono essere un qualunque numero reale.
Facciamo subito un esempio per chiarire le idee: definiamo lo script divide(a)
che divide 100 per l'argomento passato. Il dominio di "divide" non ammette lo 0 perché l'operazione 100/0 è indefinita.
In termini matematici possiamo scrivere:
In insiemistica si possono formare insiemi discreti specificando gli elementi che appartengono all'insieme tra parentesi graffe, separati da virgole. l'insieme {0} comprende quindi solo elemento 0.
/ {0} è identico a - {0}, ovvero togliamo da l'insieme {0}, per i quali valori la funzione risulta indefinita.
Con questa operazione abbiamo determinato il campo di esistenza della funzione "divide".
È importante sottolineare che le funzioni collegano univocamente un output ad ogni input. Questo significa che per ogni valore di input la funzione restituisce uno ed un solo valore in output.
Pensateci un attimo, se alla domanda "Dimmi quanto fa 100 diviso questo valore qui, 10" seguissero due o più risposte distinte tra loro ci sarebbe qualcosa che non va.
Generalmente ci si riferisce al singolo elemento del Dominio e del Codominio rispettivamente con x e y con questa notazione
y = f(x). Nel nostro esempio, y = divide(x). Potremmmo scrivere una "tabella di verità" della funzione divide di questo tipo:
se x è 4, y = 25 (infatti 100/4 = 25)
x -> y
4 -> 25 (=100/4)
10 -> 10 (=100/10)
2 -> 50
1 -> 100
x è anche chiamata variabile indipendente perché può essere scelta arbitrariamente, mentre y è variabile dipende, perché è conseguenza logica (dipende da..) della scelta fatta per x.
Per le funzioni del tipo
possiamo disegnare una rappresentazione grafica sul piano cartesiano in due dimensioni. Tipicamente si assegna l'asse X al Dominio (infatti il singolo elemento è x), e l'asse Y al Codominio.
Il grafico sarà un insieme (infinito, se la funzione è continua) di punti del tipo P(x,y) dove x,y rispettano la tavola di verità scritta prima.
Il grafico di y = 100/x = 1/x * 100 è simile a y = 1/x, perché, come vedremo più avanti, moltiplicare tutto per un numero equivale alla trasformazione per "scaling". Si ingrandisce tutto, ma si mantengono le stesse proporzioni.
Una curva di questo tipo viene chiamata iperbole equilatera.
Funzioni trigonometriche
Dovete prima di tutto avere bene in mente il contesto in cui ci muoviamo, ovvero quello di circonferenza goniometrica. Immaginatevi dunque in un piano cartesiano 2D come quello precedente il grafico di una circonferenza che ha per centro l'origine O(0,0) e per raggio la lunghezza 1.
Possiamo subito affermare che tale curva interseca gli assi nei punti (1,0), (0,1), (-1,0) e (0,-1).
Come prima cosa, per lavorare con questa curva ne definiamo la lunghezza.
Immaginate di tagliare la circonferenza e di stenderla sull'asse delle X. Quello che viene fuori è un segmento della lunghezza che convenzionalmente viene considerata come due volte la costante .
Quindi:
circonferenza unitaria =(è lunga) 2
semicirconferenza unitaria =(è lunga)
3/4 di circonferenza unitaria =(è lunga) 3/4 * 2 = 3/2
Consideriamo adesso un angolo alfa e notiamo che ad ogni angolo possiamo associare univocamente una lunghezza l nella circonferenza unitaria secondo questa regola:
Il linguaggio che ho utilizzato dovrebbe ricordarvi la definizione di funzione. Ad ogni angolo espresso in gradi possiamo attribuire univocamente la lunghezza l indicata nel disegno. Ma sì! Anche questa è una funzione, e ce la offre direttamente game maker con degtorad(x), ponendo x = alfa. La lunghezza l si chiama angolo radiante corrispondente all'angolo alfa espresso in gradi.
Proviamo a scrivere una piccola tabella di verità per questa funzione:
x (gradi) -> y (radianti)
0 -> 0
180 ->
360 -> 2
Osserviamo che 360 = 2*(180) e che degtorad(360) è ovviamente 2*degtorad(180). Tutto questo può farci
pensare che la relazione di conversione sia lineare. Possiamo quindi rappresentare in un grafico i tre punti della tabella di verità e congiungerli con un segmento, e avremo la nostra funzione.
Ed ecco la formulazione matematica con annesso campo d'esistenza.
(In realtà degtorad funziona comunque da in . Più avanti si capirà come interpretare angoli negativi o più grandi di 2. Un grazie a BaronVsCorsar per la segnalazione)
Nell'ultima espressione compaiono due intervalli.
Gli intervalli che prenderemo in considerazione saranno sempre intervalli reali, ovvero sottoinsiemi limitati di . La notazione per scrivere un intervallo è la seguente: [a,b], dove a e b sono rispettivamente il limite inferiore e superiore dell'intervallo. Gli elementi dell'intervallo sono tutti i numeri reali compresi tra a e b.
Possiamo includere/escludere nell'intervallo l'estremo inferiore utilizzando rispettivamente [a,b], ]a,b].
Analogamente per l'estremo superiore: [a,b], [a,b[
Finalmente parliamo di seno e coseno.
Sono entrambe funzioni reali di variabile reale (dello stesso tipo di quelle affrontate fin'ora) che attribuiscono una lunghezza particolare ad ogni angolo. Sempre in riferimento alla circonferenza unitaria, valgono le segueneti regole:
Il dominio di entrambe è tutto . Infatti, un angolo maggiore di 2 può essere scomposto in elementi più semplici.
con n = massimo numero intero possibile.
Visto che l'angolo 2 corrisponde all'angolo 0, i valori alfa e beta, pur diversi numericamente, hanno la stessa valenza se considerati come angoli.
Per esempio, a = (13/3) può essere considerato come (4 + 1/3). Scelto n=2, a = 2n + /3.
a ha la stessa valenza di /3
Un angolo negativo si comporta come un angolo positivo:
l'unica differenza è che, per convenzione, invece di crescere a partire dall'asse X in senso antiorario, lo fa in senso orario.
Se fate girare mentalmente l'angolo alfa nel disegno tra 0 e 2 vedrete che seno e coseno oscillano sempre tra 1 e -1, non vanno mai oltre. Ne segue:
Per il grafico di seno e coseno vi invito a guardare su internet, non sarei mai in grado di disegnarvelo preciso.
Ora consideriamo questa immagine
che rappresenta il problema che ci troviamo davanti quando (succede spesso) dobbiamo utilizzare le funzioni lengthdir_x (che restituirebbe il lato OB) e lengthdir_y (che restituirebbe il lato AB).
Sul triangolo rettangolo di partenza (OAB) sono segnati i parametri che avremmo passato alle funzioni lengthdir_*.
Costruiamo un nuovo triangolo (OCD) simile ad (OAB) ma con ipotenusa (OC) lunga 1. Questo nuovo triangolo (disegnato in bianco) è inscrivibile in una circonferenza di raggio 1 con centro nell'origine del sistema cartesiano. Questo significa che i lati OD e DC equivalgono rispettivamente a cos(alfa) e sin(alfa). Visto che i due triangoli sono simili , le proporzioni tra i lati corrispondenti sono mantenute. length/OC = AB/CD = OB/OD.
Sostituendo con i valori che conosciamo:
length = AB/sin(alfa)
length = OB/cos(alfa)
da cui si ricava:
AB = length*sin(alfa)
OB = length*cos(alfa)
Consideriamo infine un problema che spesso viene affrontato dai programmatori 3D alle prime armi. Sono già uscite diverse domande su lenghtdir_z, ma sostanzialmente questa funzione non può essere simile alle altre due. Il problema di fondo è che per descrivere un punto nel piano bastano due informazioni (nel nostro caso le due coordinate polari lunghezza e "direzione" vengono convertite da lengthdir_x,y nelle corrispondenti cartesiane), mentre nel 3D, tre sono le dimensioni e tre sono le informazioni che servono per determinare un punto nello spazio. Per effettuare la conversione coordinate polari->cartesiane bisognerebbe creare nuove funzioni, diverse da quelle per il piano, che accettino tre argomenti: due angoli e una lunghezza (quelli rappresentati nel disegno seguente)
le nuove funzioni lengthdir2_x,y,z ritornano le coordinate del punto P(OB,OA,PH) individuato da alfa,beta e length.
Per quanto riguarda le prime due ce la possiamo cavare con le vecchie lengthdir_x,y considerando l'angolo alfa e la lunghezza OH.
Con un ragionamento analogo all'esempio precedente, ma riadattato al nostro caso,
possiamo calcolare:
OH = length*cos(beta)
PH = length*sin(beta)
In conclusione solo sfruttando anche la terza informazione (beta) siamo in grado di trovare tutto, la domanda su lengthdir_z(length,dir) era insensata, e, per motivi di consistenza, è anche un bene che non esista in game maker.
Una funzione matematica ha lo stesso significato logico di uno script nel GML. Immaginatevela come una scatola magica che trasforma univocamente l'input che gli viene dato in un determinato output, secondo alcune regole ben specifiche. In particolare utilizzeremo funzioni ad una sola variabile reale (un solo parametro per input, che generalmente viene chiamato x).
Funzioni di questo tipo si indicano in questo modo:
dove f è il nome della funzione. Dopo : viene specificato l'insieme Dominio -> Codominio della funzione. L'insieme Dominio è il raggruppamento di tutti i numeri che la funzione può accettare come input. Se diamo in pasto alla funzione un valore non incluso nel suo dominio in matematica si dice che la funzione non è definita. In programmazione una cosa del genere potrebbe condurre ad un comportamento inaspettato della funzione.
Similmente, il Codominio di una funzione è l'insieme dei valori che la funzione può restituire.
Nel nostro caso sia Dominio che Codominio sono , a significare che input e output possono essere un qualunque numero reale.
Facciamo subito un esempio per chiarire le idee: definiamo lo script divide(a)
Codice: Seleziona tutto
return(100/argument1);
In termini matematici possiamo scrivere:
In insiemistica si possono formare insiemi discreti specificando gli elementi che appartengono all'insieme tra parentesi graffe, separati da virgole. l'insieme {0} comprende quindi solo elemento 0.
/ {0} è identico a - {0}, ovvero togliamo da l'insieme {0}, per i quali valori la funzione risulta indefinita.
Con questa operazione abbiamo determinato il campo di esistenza della funzione "divide".
È importante sottolineare che le funzioni collegano univocamente un output ad ogni input. Questo significa che per ogni valore di input la funzione restituisce uno ed un solo valore in output.
Pensateci un attimo, se alla domanda "Dimmi quanto fa 100 diviso questo valore qui, 10" seguissero due o più risposte distinte tra loro ci sarebbe qualcosa che non va.
Generalmente ci si riferisce al singolo elemento del Dominio e del Codominio rispettivamente con x e y con questa notazione
y = f(x). Nel nostro esempio, y = divide(x). Potremmmo scrivere una "tabella di verità" della funzione divide di questo tipo:
se x è 4, y = 25 (infatti 100/4 = 25)
x -> y
4 -> 25 (=100/4)
10 -> 10 (=100/10)
2 -> 50
1 -> 100
x è anche chiamata variabile indipendente perché può essere scelta arbitrariamente, mentre y è variabile dipende, perché è conseguenza logica (dipende da..) della scelta fatta per x.
Per le funzioni del tipo
possiamo disegnare una rappresentazione grafica sul piano cartesiano in due dimensioni. Tipicamente si assegna l'asse X al Dominio (infatti il singolo elemento è x), e l'asse Y al Codominio.
Il grafico sarà un insieme (infinito, se la funzione è continua) di punti del tipo P(x,y) dove x,y rispettano la tavola di verità scritta prima.
Il grafico di y = 100/x = 1/x * 100 è simile a y = 1/x, perché, come vedremo più avanti, moltiplicare tutto per un numero equivale alla trasformazione per "scaling". Si ingrandisce tutto, ma si mantengono le stesse proporzioni.
Una curva di questo tipo viene chiamata iperbole equilatera.
Funzioni trigonometriche
Dovete prima di tutto avere bene in mente il contesto in cui ci muoviamo, ovvero quello di circonferenza goniometrica. Immaginatevi dunque in un piano cartesiano 2D come quello precedente il grafico di una circonferenza che ha per centro l'origine O(0,0) e per raggio la lunghezza 1.
Possiamo subito affermare che tale curva interseca gli assi nei punti (1,0), (0,1), (-1,0) e (0,-1).
Come prima cosa, per lavorare con questa curva ne definiamo la lunghezza.
Immaginate di tagliare la circonferenza e di stenderla sull'asse delle X. Quello che viene fuori è un segmento della lunghezza che convenzionalmente viene considerata come due volte la costante .
Quindi:
circonferenza unitaria =(è lunga) 2
semicirconferenza unitaria =(è lunga)
3/4 di circonferenza unitaria =(è lunga) 3/4 * 2 = 3/2
Consideriamo adesso un angolo alfa e notiamo che ad ogni angolo possiamo associare univocamente una lunghezza l nella circonferenza unitaria secondo questa regola:
Il linguaggio che ho utilizzato dovrebbe ricordarvi la definizione di funzione. Ad ogni angolo espresso in gradi possiamo attribuire univocamente la lunghezza l indicata nel disegno. Ma sì! Anche questa è una funzione, e ce la offre direttamente game maker con degtorad(x), ponendo x = alfa. La lunghezza l si chiama angolo radiante corrispondente all'angolo alfa espresso in gradi.
Proviamo a scrivere una piccola tabella di verità per questa funzione:
x (gradi) -> y (radianti)
0 -> 0
180 ->
360 -> 2
Osserviamo che 360 = 2*(180) e che degtorad(360) è ovviamente 2*degtorad(180). Tutto questo può farci
pensare che la relazione di conversione sia lineare. Possiamo quindi rappresentare in un grafico i tre punti della tabella di verità e congiungerli con un segmento, e avremo la nostra funzione.
Ed ecco la formulazione matematica con annesso campo d'esistenza.
(In realtà degtorad funziona comunque da in . Più avanti si capirà come interpretare angoli negativi o più grandi di 2. Un grazie a BaronVsCorsar per la segnalazione)
Nell'ultima espressione compaiono due intervalli.
Gli intervalli che prenderemo in considerazione saranno sempre intervalli reali, ovvero sottoinsiemi limitati di . La notazione per scrivere un intervallo è la seguente: [a,b], dove a e b sono rispettivamente il limite inferiore e superiore dell'intervallo. Gli elementi dell'intervallo sono tutti i numeri reali compresi tra a e b.
Possiamo includere/escludere nell'intervallo l'estremo inferiore utilizzando rispettivamente [a,b], ]a,b].
Analogamente per l'estremo superiore: [a,b], [a,b[
Finalmente parliamo di seno e coseno.
Sono entrambe funzioni reali di variabile reale (dello stesso tipo di quelle affrontate fin'ora) che attribuiscono una lunghezza particolare ad ogni angolo. Sempre in riferimento alla circonferenza unitaria, valgono le segueneti regole:
Il dominio di entrambe è tutto . Infatti, un angolo maggiore di 2 può essere scomposto in elementi più semplici.
con n = massimo numero intero possibile.
Visto che l'angolo 2 corrisponde all'angolo 0, i valori alfa e beta, pur diversi numericamente, hanno la stessa valenza se considerati come angoli.
Per esempio, a = (13/3) può essere considerato come (4 + 1/3). Scelto n=2, a = 2n + /3.
a ha la stessa valenza di /3
Un angolo negativo si comporta come un angolo positivo:
l'unica differenza è che, per convenzione, invece di crescere a partire dall'asse X in senso antiorario, lo fa in senso orario.
Se fate girare mentalmente l'angolo alfa nel disegno tra 0 e 2 vedrete che seno e coseno oscillano sempre tra 1 e -1, non vanno mai oltre. Ne segue:
Per il grafico di seno e coseno vi invito a guardare su internet, non sarei mai in grado di disegnarvelo preciso.
Ora consideriamo questa immagine
che rappresenta il problema che ci troviamo davanti quando (succede spesso) dobbiamo utilizzare le funzioni lengthdir_x (che restituirebbe il lato OB) e lengthdir_y (che restituirebbe il lato AB).
Sul triangolo rettangolo di partenza (OAB) sono segnati i parametri che avremmo passato alle funzioni lengthdir_*.
Costruiamo un nuovo triangolo (OCD) simile ad (OAB) ma con ipotenusa (OC) lunga 1. Questo nuovo triangolo (disegnato in bianco) è inscrivibile in una circonferenza di raggio 1 con centro nell'origine del sistema cartesiano. Questo significa che i lati OD e DC equivalgono rispettivamente a cos(alfa) e sin(alfa). Visto che i due triangoli sono simili , le proporzioni tra i lati corrispondenti sono mantenute. length/OC = AB/CD = OB/OD.
Sostituendo con i valori che conosciamo:
length = AB/sin(alfa)
length = OB/cos(alfa)
da cui si ricava:
AB = length*sin(alfa)
OB = length*cos(alfa)
Consideriamo infine un problema che spesso viene affrontato dai programmatori 3D alle prime armi. Sono già uscite diverse domande su lenghtdir_z, ma sostanzialmente questa funzione non può essere simile alle altre due. Il problema di fondo è che per descrivere un punto nel piano bastano due informazioni (nel nostro caso le due coordinate polari lunghezza e "direzione" vengono convertite da lengthdir_x,y nelle corrispondenti cartesiane), mentre nel 3D, tre sono le dimensioni e tre sono le informazioni che servono per determinare un punto nello spazio. Per effettuare la conversione coordinate polari->cartesiane bisognerebbe creare nuove funzioni, diverse da quelle per il piano, che accettino tre argomenti: due angoli e una lunghezza (quelli rappresentati nel disegno seguente)
le nuove funzioni lengthdir2_x,y,z ritornano le coordinate del punto P(OB,OA,PH) individuato da alfa,beta e length.
Per quanto riguarda le prime due ce la possiamo cavare con le vecchie lengthdir_x,y considerando l'angolo alfa e la lunghezza OH.
Con un ragionamento analogo all'esempio precedente, ma riadattato al nostro caso,
possiamo calcolare:
OH = length*cos(beta)
PH = length*sin(beta)
In conclusione solo sfruttando anche la terza informazione (beta) siamo in grado di trovare tutto, la domanda su lengthdir_z(length,dir) era insensata, e, per motivi di consistenza, è anche un bene che non esista in game maker.
(1) Lo spoiler seguente contiene:
- definizione e rappresentazione di vettori
- operazioni di somma, prodotto tra vettori
- rappresentazione per combinazione lineare di una base
Spoiler
Un vettore è una collezione più o meno grande di elementi omogenei matematici. Con elementi matematici intendo veramente una qualunque classe di elementi, a patto che per questa siano definiti operatori di somma e moltiplicazione (tra poco vedremo questa particolarità più nello specifico).
Nel corso di questa guida tratteremo soltanto vettori di numeri reali,quindi d'ora in poi quando parlerò di vettori mi riferirò sempre e soltanto a questo tipo di collezione.
I vettori canonici sono formati da tre numeri reali. Ognuno dei tre numeri è anche chiamato "componente" del vettore. Si è soliti riferirsi alle tre componenti dei vettori con il nome x,y,z, oppure, molto più spesso, se il vettore si chiama "v", le tre componenti si chiamano , , .
Fondamentalmente le informazioni (le componenti) di un vettore "codificano" solamente una direzione nello spazio e, in essa, un verso di percorrenza. Questi vettori vengono anche chiamati liberi e sono rappresentati con un trattino inferiore (come una sottolineatura). Quando questi vettori vengono applicati ad un certo punto nello spazio vengono detti appunto vettori applicati. Spesso i vettori liberi vengono applicati all'origine (0,0,0) e, quindi, le tre componenti possono essere considerate come il punto (0+, 0+, 0+) = (, , ).
I vettori applicati si rappresentano con la famosa freccina sopra il nome, nel nostro caso .
Per semplicità userò quest'ultima notazione per riferirmi a qualunque vettore, sia esso applicato o libero.
La figura seguente mostra la rappresentazione grafica di un vettore dalle componenti 4,4 applicato all'origine in un piano cartesiano 2D. Esso può anche essere considerato come il punto P a cui "punta" (perdonate la sprecisione del disegno, ho fatto a mano..)
Si definisce modulo di un vettore la sua intensità o lunghezza. Per chiarire le idee, consideriamo un vettore applicato all'origine. Il modulo di questo vettore è la sua distanza dall'origine. Si scrive:
, formula che si può ricavare dal teorema di pitagora.
Presi due vettori e , per poter essere chiamati tali, e, quindi, essere elementi di (l'insieme dei vettori con 3 numeri reali), devono soddisfare le seguenti condizioni:
(1.1) ( + ) ,
"il vettore risultante dalla somma di due vettori u,v appartiene a V3, per ogni coppia u,v appartenente a V3 scelta"
(1.2) c , c
"il vettore risultante dal prodotto di un vettore u per un numero reale c appartiene a V3 per ogni coppia <vettore v di V3, numero c reale> scelta "
(1.3) ( x ) ,
"il vettore risultante dal prodotto vettoriale uxv appartiene a V3, per ogni coppia u,v appartenente a V3 scelta"
La prima espressione mostra la somma tra due vettori, che produce un nuovo vettore che ha per componenti la somma membro a membro dei componenti dei due vettori iniziali.
per rappresentare il vettore w è stata usata la notazione per parentesi quadre che racchiude le componenti di w separate dalle virgole.
Nell'espressione (1.2) compare il prodotto tra un vettore ed un numero reale.Questo prodotto restituisce un vettore le cui componenti sono quelle del vettore di partenza, ciascuna moltiplicata per il numero reale.
Com'è facile intuire, moltiplicare un vettore per uno scalare (un numero reale) equivale alla nota trasformazione per scaling o stretching. Moltiplicando un vettore per 2 otterremo un vettore orientato allo stesso modo ma lungo il doppio; possiamo dimezzarlo moltiplicandolo per 0.5, e così via.
La terza espressione mostra, infine, il prodotto vettoriale tra due vettori, che restituisce ancora un vettore secondo questa regola: (sia alfa l'angolo compreso tra i due vettori)
(1.4)
Questa formula introduce un nuovo vettore .
I vettori scritti in questa notazione sono detti vettori normali e hanno modulo pari a 1.
Questa peculiarità li rende particolarmente comodi, vedremo perché.
In questo caso è da considerare perpendicolare a entrambi i vettori di partenza u e v. L'espressione a destra di è chiaramente una quantità scalare.
Si tratta quindi di un'espressione analoga alla (1.2). Essendo il modulo di pari a 1, il prodotto a (con a scalare) non farà altro che cambiare il modulo di in a.
è dunque semplicemente un manichino, un vettore facilmente modellabile che quindi torna comodo nelle definizioni.
È facile dimostrare che le tre condizioni (1.1),(1.2),(1.3) sono sempre soddisfatte finché si tratta di vettori a numeri reali.
Consideriamo adesso un famoso insieme costituito da tre vettori normali, tutti perpendicolari tra loro:
{ ,, }
= [1,0,0]
= [0,1,0]
= [0,0,1]
Questi tre costituiscono una base che genera . Un insieme di vettori costituisce una base per uno spazio vettoriale (in questo caso ) quando qualunque vettore di può essere espresso come un'unica combinazione lineare dei vettori della base
Per esempio il vettore = [4,5,2] si può scrivere come:
4 + 5 + 2 .
Un'altro modo molto usato per scrivere la stessa cosa è:
Ancora una cosa: anche se avete letto tutta questa sezione potrebbe essere consigliabile fare un po' di esercizio su carta per prenderci la mano. Inventate qualche vettore e giù di somme/differenze/prodotti per allenare la vostra mente a visualizzare il rapidamente il risultato delle operazioni più comuni. La differenza tra due vettori può essere scritta a partire dalla somma (es: + (- ), essendo - un'espressione del tipo (1.2))
Nel corso di questa guida tratteremo soltanto vettori di numeri reali,quindi d'ora in poi quando parlerò di vettori mi riferirò sempre e soltanto a questo tipo di collezione.
I vettori canonici sono formati da tre numeri reali. Ognuno dei tre numeri è anche chiamato "componente" del vettore. Si è soliti riferirsi alle tre componenti dei vettori con il nome x,y,z, oppure, molto più spesso, se il vettore si chiama "v", le tre componenti si chiamano , , .
Fondamentalmente le informazioni (le componenti) di un vettore "codificano" solamente una direzione nello spazio e, in essa, un verso di percorrenza. Questi vettori vengono anche chiamati liberi e sono rappresentati con un trattino inferiore (come una sottolineatura). Quando questi vettori vengono applicati ad un certo punto nello spazio vengono detti appunto vettori applicati. Spesso i vettori liberi vengono applicati all'origine (0,0,0) e, quindi, le tre componenti possono essere considerate come il punto (0+, 0+, 0+) = (, , ).
I vettori applicati si rappresentano con la famosa freccina sopra il nome, nel nostro caso .
Per semplicità userò quest'ultima notazione per riferirmi a qualunque vettore, sia esso applicato o libero.
La figura seguente mostra la rappresentazione grafica di un vettore dalle componenti 4,4 applicato all'origine in un piano cartesiano 2D. Esso può anche essere considerato come il punto P a cui "punta" (perdonate la sprecisione del disegno, ho fatto a mano..)
Si definisce modulo di un vettore la sua intensità o lunghezza. Per chiarire le idee, consideriamo un vettore applicato all'origine. Il modulo di questo vettore è la sua distanza dall'origine. Si scrive:
, formula che si può ricavare dal teorema di pitagora.
Presi due vettori e , per poter essere chiamati tali, e, quindi, essere elementi di (l'insieme dei vettori con 3 numeri reali), devono soddisfare le seguenti condizioni:
(1.1) ( + ) ,
"il vettore risultante dalla somma di due vettori u,v appartiene a V3, per ogni coppia u,v appartenente a V3 scelta"
(1.2) c , c
"il vettore risultante dal prodotto di un vettore u per un numero reale c appartiene a V3 per ogni coppia <vettore v di V3, numero c reale> scelta "
(1.3) ( x ) ,
"il vettore risultante dal prodotto vettoriale uxv appartiene a V3, per ogni coppia u,v appartenente a V3 scelta"
La prima espressione mostra la somma tra due vettori, che produce un nuovo vettore che ha per componenti la somma membro a membro dei componenti dei due vettori iniziali.
per rappresentare il vettore w è stata usata la notazione per parentesi quadre che racchiude le componenti di w separate dalle virgole.
Nell'espressione (1.2) compare il prodotto tra un vettore ed un numero reale.Questo prodotto restituisce un vettore le cui componenti sono quelle del vettore di partenza, ciascuna moltiplicata per il numero reale.
Com'è facile intuire, moltiplicare un vettore per uno scalare (un numero reale) equivale alla nota trasformazione per scaling o stretching. Moltiplicando un vettore per 2 otterremo un vettore orientato allo stesso modo ma lungo il doppio; possiamo dimezzarlo moltiplicandolo per 0.5, e così via.
La terza espressione mostra, infine, il prodotto vettoriale tra due vettori, che restituisce ancora un vettore secondo questa regola: (sia alfa l'angolo compreso tra i due vettori)
(1.4)
Questa formula introduce un nuovo vettore .
I vettori scritti in questa notazione sono detti vettori normali e hanno modulo pari a 1.
Questa peculiarità li rende particolarmente comodi, vedremo perché.
In questo caso è da considerare perpendicolare a entrambi i vettori di partenza u e v. L'espressione a destra di è chiaramente una quantità scalare.
Si tratta quindi di un'espressione analoga alla (1.2). Essendo il modulo di pari a 1, il prodotto a (con a scalare) non farà altro che cambiare il modulo di in a.
è dunque semplicemente un manichino, un vettore facilmente modellabile che quindi torna comodo nelle definizioni.
È facile dimostrare che le tre condizioni (1.1),(1.2),(1.3) sono sempre soddisfatte finché si tratta di vettori a numeri reali.
Consideriamo adesso un famoso insieme costituito da tre vettori normali, tutti perpendicolari tra loro:
{ ,, }
= [1,0,0]
= [0,1,0]
= [0,0,1]
Questi tre costituiscono una base che genera . Un insieme di vettori costituisce una base per uno spazio vettoriale (in questo caso ) quando qualunque vettore di può essere espresso come un'unica combinazione lineare dei vettori della base
Per esempio il vettore = [4,5,2] si può scrivere come:
4 + 5 + 2 .
Un'altro modo molto usato per scrivere la stessa cosa è:
Ancora una cosa: anche se avete letto tutta questa sezione potrebbe essere consigliabile fare un po' di esercizio su carta per prenderci la mano. Inventate qualche vettore e giù di somme/differenze/prodotti per allenare la vostra mente a visualizzare il rapidamente il risultato delle operazioni più comuni. La differenza tra due vettori può essere scritta a partire dalla somma (es: + (- ), essendo - un'espressione del tipo (1.2))
Il prossimo piccolo, grande passo: le matrici.
Lo prometto: da qui in avanti gli argomenti si faranno sempre più interessanti e vicini al 3D.
(2) Nello spoiler seguente parleremo di:
- sistemi lineari
- matrice associata ad un sistema
- operazioni tra matrici
- matrice inversa
- compatibilità di un sistema
- determinante di una matrice quadrata di terzo ordine
Spoiler
Sistemi lineari in
Un sistema di equazioni raccoglie diverse uguaglianze che hanno in comune una stessa n-upla di variabili (insieme di n variabili).
Ogni sistema può avere una,diverse,infinite o nessuna soluzione. Ogni soluzione è una n-upla di variabili che soddisfa contemporaneamente tutte le uguaglianze del sistema. Per quanto richiede la guida possiamo limitarci a trattare sistemi di tre variabili (x,y,z).
Identificheremo con = [ , , ] la soluzione particolare di un sistema a tre variabili reali.
Per esempio, supponiamo di voler traslare il punto P di coordinate ( , , ) in una nuova posizione P'. Sia il vettore che collega P a P'.
Possiamo servirci del seguente sistema:
essendo ( , , ) le nuove coordinate di P (traslato in P').
Si noti che solo le coordinate di P' sono variabili, tutto il resto (le coordinate di P e le componenti del vettore ) sono espressioni costanti.
Potreste storcere il naso all'idea che le coordinate di P' siano variabili. Lo sono fintanto che non le inseriamo dentro quel sistema. È proprio il sistema, infatti, a dire che le ( , , )
devono essere tali da soddisfare contemporaneamente le tre condizioni affinché P' si trovi in (P + ).
In particolare il sistema analizzato ha un'unica soluzione, pertanto le coordinate di P' sono costanti e corrispondono alla soluzione del sistema.
Il sistema analizzato è estremamente semplice: la sua soluzione è immediata.
Prima di passare oltre vorrei fare notare che un sistema a tre variabili, per ammettere un'unica soluzione, deve avere almeno tre equazioni, non di meno. Più avanti analizzeremo più nel dettaglio questo discorso, per adesso limitiamoci a ricordare l'esempio della sezione precedente del lengthdir_z (se non lo avete visto si trova alla fine di trigonometria, il primo spoiler).
Il discorso era simile, per lavorare in 3D servono sempre almeno 3 informazioni, poi se ne vengono date altre ridondanti poco ci importa, sarà più facile trovare la soluzione.
Voglio far notare anche che attraverso questo sistema abbiamo applicato una trasformazione di coordinate per traslazione, il che potrebbe farci pensare (correttamente) di riuscire a tirar fuori dal cilindro anche rotazioni e ridimensionamenti (perdonate la parola, non so come chiamare lo scaling) con sistemi simili. Sono sicuro che cominciate già a sentire il potenziale nascosto di quanto stiamo vedendo, ma mettete le fantasie da parte, andiamo avanti analizzando il caso generico in cui non troviamo soltanto espressioni costanti (sarebbe bello!).
un sistema di questo tipo si chiama anche lineare perché le variabili che compaiono sono sempre di primo grado.
Come vedete adesso ogni equazione contiene tutte e tre le variabili (x1,x2,x3 non sono più le coordiante di P ma le variabili del sistema), ciascuna moltiplicata per una costante reale. In ogni uguaglianza a destra dell'uguale c'è ancora una costante reale (ho usato gli indici per non dover inventare troppi nomi). La dove vedete delle lettere che non siano x, quindi, dovete immaginarvi al loro posto semplicemente un numero.
Si può notare che si tratta veramente del caso generale, qualunque sistema compatibile (che ammette una soluzione) in tre variabili può essere espresso a partire da questa bestia attribuendo i giusti valori a ogni costante.
Quello precedente, per esempio, si ottiene con: a1 = 1, b1 = 0, c1 = 0, d1 = x1 + v1 e così via per le altre righe.
Per studiare al meglio questo sistema devo introdurre il concetto di matrice.
Attraverso le matrici potremo studiare esattamente come, quando e quante soluzioni ammette un sistema del genere, e perfino trovare un modo automatico per risolverli, senza doverci ragionare troppo.
Matrici
Rispondo subito alla prima e ovvia domanda: cos'è una matrice?
Osserviamo subito un esempio
come vedete, una matrice è semplicemente una griglia di valori reali.
Una matrice può avere un qualunque m >= 1 numero di righe e un qualunque n >= 1 numero di colonne.
Generalmente il nome delle matrici è una lettera maiuscola.
La notazione completa per riferirsi ad una matrice con m righe ed n colonne è , che nel nostro esempio diventa . Una matrice con n=m (come A) è detta quadrata.
Un'altra matrice particolare è quella che viene definita vettore colonna, che ha m > 1 righe ed una sola colonna.
Una matrice di questo tipo sostanzialmente non è diversa da un vettore di .
Per riferirsi allo specifico elemento di A nella i-esima riga e j-esima colonna si scrive . La matrice precedente può quindi essere riscritta come segue:
.
Tra le matrici sono definite le operazioni di:
- somma / differenza
- prodotto
Inoltre abbiamo anche l'operazione di:
- prodotto / divisione per uno scalare.
Partiamo dalla terza operazione. Moltiplicare (dividere) una matrice A per un numero k significa moltiplicare (dividere) ogni suo elemento per k... abbastanza intuibile, no?
(2.1)
Se volessi dividere A per h potrei semplicemente moltiplicarla per 1/h.
La somma tra due matrici, rigorosamente della stessa dimensione, segue la seguente regola:
(2.2)
La differenza tra due matrici segue dalla somma moltiplicando B per (-1)
(2.3)
Infine il prodotto tra due matrici, l'operazione che sicuramente ora come ora considererete la più difficile e/o astrusa, ma vi assicuro che se avete pazienza e andate avanti a leggere capirete perché è stato definito così.
Il prodotto tra due matrici viene chiamato prodotto righe per colonne , fondamentalmente perché da luogo ad una nuova matrice le cui componenti sono una somma di prodotto tra gli elementi di una riga di una matrice e di una colonna dell'altra.
Vale la seguente regola:
(2.4)
Posso spenderci qualche parola, ma comunque la cosa migliore è che proviate a inventare due matrici e a calcolarne la matrice prodotto per allenarvi a capire il meccanismo.
In pratica l'elemento i-j esimo di C si ottiene sommando i contributi di coppie di numeri moltiplicati tra loro.
Le coppie si scelgono muovendosi lungo la i-esima riga della matrice A (da sinistra verso destra) e lungo la j-esima colonna di B (dall'alto verso il basso). Questa definizione implica che non tutte le matrici possono essere moltiplicate tra loro, e che il prodotto tra due matrici cambia a seconda dell'ordine (A*B != B*A).
In particolare, affinché non restino valori scoppiati nella sommatoria precedente, è necessario che il numero di colonne di A (matrice a sinistra del prodotto) sia uguale al numero di righe di B (matrice a destra).
Consideriamo A(2,3) e B(3,1). Notiamo che il prodotto A*B è definito (si noti che B è una matrice "vettore colonna"),
e non solo B*A potrebbe essere diverso, ma in questo caso è addirittura indefinito, in quanto B ha 1 colonna mentre A ha 2 righe.
Non posso davvero rendervi le cose più semplici, è necessario che fate pratica voi.
Forza, prendete carta e matita e calcolatevi ogni singolo elemento di una matrice prodotto tra due matrici quadrate di terzo grado. Se non ci prendete la mano non possiamo andare avanti.
Riprendiamo in mano il sistema lineare precedente e costruiamone la matrice associata
Come vedete le componenti della matrice associata A sono ordinatamente i coefficienti delle variabili del sistema lineare.
Costruiamo il vettore colonna D
.
Chiamiamo X il vettore colonna che ha ordinatamente come componenti x1,x2,x3 (le variabili del sistema).
Adesso vi chiedo di espandere su carta, per esercizio, l'espressione matriciale
Per espandere intendo scrivere tutte le matrici in nella forma che mostra tutte le loro componenti. Ovviamente prima dovete calcolare la matrice risultate da A*X.
Fate un favore a voi stessi: impeditevi di leggere oltre finché non avete finito l'esercizio.
Se è andato tutto bene dovreste aver capito che
è un'altro modo di scrivere lo stesso sistema, ma molto più sintetico.
Se dovessimo imparare a maneggiare espressioni matriciali di questo tipo trovare la soluzione (le componenti della matrice vettore colonna X) potrebbe diventare una passeggiata.
Intanto proviamo a comportarci come se fosse un'espressione lineare. La soluzione si troverebbe moltiplicando ambo i membri per (ricordo che elevare un valore a (-1) equivale a farne il reciproco, ) . Otterremmo X = D/A.
In altre parole se riuscissimo a definire l'operatore di divisione tra matrici avremmo fatto il nostro lavoro.
Piccola nota: d'ora in poi considereremo matrici quadrate di ordine 3, che sono le più comuni per questi calcoli.
Introduciamo allora la matrice Identità, chiamata spesso I (Identity Matrix).
Questa è una matrice quadrata che ha tutti gli elementi della diagonale principale pari a 1, gli altri sono 0.
Inventate una qualunque matrice A3,3 e noterete che IA = AI = A.
Dimostriamolo:
notiamo che tutte le espressioni in cui si espande la sommatoria sono 0 eccetto quella in cui = 1, ovvero nell'unico caso in cui k=i.
La sommatoria si traduce quindi in:
concludiamo che B = A.
Potremmo considerare la matrice Identità in analogia con il numero reale 1 per le equazioni lineari:
1*a = a*1 = a;
Chiamiamo ora quella matrice che moltiplicata per A restituisce I.
Vorrei specificare bene che non è elevazione di A a (-1), è pura notazione, esattamente come non è ma la funzione inversa di f. Di fatto non esiste l'operazione di dividere qualcosa per una matrice, però per le sue caratteristiche possiamo immaginare (per capire meglio) che sia 1/A
(1/A * A farebbe, infatti, 1, che come ho detto prima può essere associato alla matrice identità I, ma di fatto 1 != I).
Osserviamo che per come la ho definita, deve avere dimensione 3,3. Infatti, essendo definiti entrambi i prodotti destro e sinistro di con A, deve avere lo stesso numero di colonne e di righe di A.
Sempre in analogia con i numeri reali possiamo notare che:
infatti a^(-1) = 1/a.
Moltiplicare una matrice per non significa proprio "dividerla" per A , ma è la cosa più vicina alla divisione che siamo riusciti a fare.
Riconsideriamo il sistema lineare precedente,
Moltiplicando sia a sinistra che a destra dell'uguale per una stessa matrice, l'uguaglianza rimane intatta:
Si noti che l'ordine dei prodotto nella prima espressione è stato mantenuto: è stata posizionata a sinistra di entrambe le parti.
L'ultima espressione mostra che per risolvere un sistema è sufficiente invertirne la matrice associata.
Trovare le componenti della matrice è un lavoro lungo e faticoso, adatto più ad un processore che all'uomo per la sua mole computazionale, ma una volta fatto consente di risolvere velocemente sistemi anche molto complessi e per questo è diventato il metodo più utilizzato per portare a termine le trasformazioni nei vertici in 3D.
è chiamata matrice inversa di A.
Cominciamo dicendo che non tutte le matrici sono invertibili, ovvero non tutte le matrici A ammettono tale che:
Per esempio, se il sistema cui la matrice A è associata fosse incompatibile, dovremmo pensare a
D come una matrice indefinita.
Visto che D è un vettore colonna costante, è proprio ad essere indefinita: si tratta di un caso in cui A non ammette inversa. Viceversa, se A ammette inversa, necessariamente D è soluzione del sistema .
Possiamo dire con certezza, quindi, che un sistema è compatibile se la sua matrice associata A ammette inversa.
Come ho già detto, invertire una matrice è un processo lungo e faticoso, e tale è anche la sua spiegazione.
A meno che non siate voi a chiedermi di farla, in questa sede non vi spiegherò come si invertono le matrici.
Tuttavia capisco che il discorso della compatibilità e delle dimensioni di un sistema possa comunque essere interessante, perciò possiamo focalizzarci su quello.
Consideriamo dunque , sempre in riferimento al sistema , la matrice
Essendo tale matrice uguale a D vale, per ogni riga i, la seguente uguaglianza:
Un sistema compatibile ammette la soluzione particolare [x1,x2,x3] che soddisfa le tre espressioni ottenute al variare di i da quella precedente.
Riscriviamo il sistema in una nuova forma
Potete verificare che si tratta della stessa cosa facendo uso delle proprietà 2.1 e 2.2 delle matrici (prodotto per numero reale e somma).
In questa forma però alcuni vettori chiave (che chiameremo A', B' ,C', e D' con ovvio significato dei nomi) sono stati isolati, inoltre appare chiaro che D' non è altro che una combinazione lineare dei vettori A' , B' , C'.
Una combinazione lineare di tre vettori restituisce un nuovo vettore che, in parole povere, "eredita" un tot dal primo, un tot dal secondo e un tot dal terzo.
Per spiegarmi meglio faccio un esempio. In precedenza abbiamo visto che un vettore può essere rappresentato come combinazione lineare dei vettori di una base
in
si ottiene prendendo volte , volte e volte .
In questo caso D' si ottiene prendendo x1 volte A', x2 volte B' e x3 volte C'.
Adesso, visto che D' potrebbe rappresentare un qualunque punto nello spazio, condizione sufficiente affinché un sistema sia compatibile è che i vettori colonna della sua matrice associata (ovvero A', B', C') non siano complanari.
Se lo fossero, infatti, non c'è santo che tenga: qualunque combinazione lineare di tre vettori complanari non potrà fare altro che originare un vettore che porta ad un punto ancora sullo stesso piano a cui appartengono i primi. Questo significa che se D' non appartiene anch'esso allo stesso piano, non esistono tre valori x1,x2,x3 che risolvano il sistema.
Ci resta ancora un problema, come verifichiamo che i vettori non siano complanari?
La risposta è semplice. Proviamo a costruire un parallelepipedo con i tre vettori che ci interessano, come quello in figura (ringrazio Wikipedia)
Si può dimostrare che il volume di questo solido equivale a fare il prodotto misto tra i tre vettori, indifferentemente dall'ordine con cui li moltiplichiamo, purché si tenga presente che il prodotto vettoriale è definito solo tra due matrici (non potete svolgere prima quello scalare, che restituisce un numero).
Quando il volume di questo solido è 0 significa che i vettori sono complanari.
Ad ogni matrice quadrata di terzo ordine possiamo attribuire un valore numerico che corrisponde al prodotto misto calcolato tra i suoi tre vettori colonna. Questo numero si chiama determinante della matrice considerata.
Se il determinante della matrice A associata ad un sistema è nullo, il sistema risulta incompatibile.
Peraltro per calcolare la matrice inversa , tra le altre operazioni, la matrice A viene divisa per il suo determinante, e sappiamo che dividere per 0 è indefinito.
Un sistema di equazioni raccoglie diverse uguaglianze che hanno in comune una stessa n-upla di variabili (insieme di n variabili).
Ogni sistema può avere una,diverse,infinite o nessuna soluzione. Ogni soluzione è una n-upla di variabili che soddisfa contemporaneamente tutte le uguaglianze del sistema. Per quanto richiede la guida possiamo limitarci a trattare sistemi di tre variabili (x,y,z).
Identificheremo con = [ , , ] la soluzione particolare di un sistema a tre variabili reali.
Per esempio, supponiamo di voler traslare il punto P di coordinate ( , , ) in una nuova posizione P'. Sia il vettore che collega P a P'.
Possiamo servirci del seguente sistema:
essendo ( , , ) le nuove coordinate di P (traslato in P').
Si noti che solo le coordinate di P' sono variabili, tutto il resto (le coordinate di P e le componenti del vettore ) sono espressioni costanti.
Potreste storcere il naso all'idea che le coordinate di P' siano variabili. Lo sono fintanto che non le inseriamo dentro quel sistema. È proprio il sistema, infatti, a dire che le ( , , )
devono essere tali da soddisfare contemporaneamente le tre condizioni affinché P' si trovi in (P + ).
In particolare il sistema analizzato ha un'unica soluzione, pertanto le coordinate di P' sono costanti e corrispondono alla soluzione del sistema.
Il sistema analizzato è estremamente semplice: la sua soluzione è immediata.
Prima di passare oltre vorrei fare notare che un sistema a tre variabili, per ammettere un'unica soluzione, deve avere almeno tre equazioni, non di meno. Più avanti analizzeremo più nel dettaglio questo discorso, per adesso limitiamoci a ricordare l'esempio della sezione precedente del lengthdir_z (se non lo avete visto si trova alla fine di trigonometria, il primo spoiler).
Il discorso era simile, per lavorare in 3D servono sempre almeno 3 informazioni, poi se ne vengono date altre ridondanti poco ci importa, sarà più facile trovare la soluzione.
Voglio far notare anche che attraverso questo sistema abbiamo applicato una trasformazione di coordinate per traslazione, il che potrebbe farci pensare (correttamente) di riuscire a tirar fuori dal cilindro anche rotazioni e ridimensionamenti (perdonate la parola, non so come chiamare lo scaling) con sistemi simili. Sono sicuro che cominciate già a sentire il potenziale nascosto di quanto stiamo vedendo, ma mettete le fantasie da parte, andiamo avanti analizzando il caso generico in cui non troviamo soltanto espressioni costanti (sarebbe bello!).
un sistema di questo tipo si chiama anche lineare perché le variabili che compaiono sono sempre di primo grado.
Come vedete adesso ogni equazione contiene tutte e tre le variabili (x1,x2,x3 non sono più le coordiante di P ma le variabili del sistema), ciascuna moltiplicata per una costante reale. In ogni uguaglianza a destra dell'uguale c'è ancora una costante reale (ho usato gli indici per non dover inventare troppi nomi). La dove vedete delle lettere che non siano x, quindi, dovete immaginarvi al loro posto semplicemente un numero.
Si può notare che si tratta veramente del caso generale, qualunque sistema compatibile (che ammette una soluzione) in tre variabili può essere espresso a partire da questa bestia attribuendo i giusti valori a ogni costante.
Quello precedente, per esempio, si ottiene con: a1 = 1, b1 = 0, c1 = 0, d1 = x1 + v1 e così via per le altre righe.
Per studiare al meglio questo sistema devo introdurre il concetto di matrice.
Attraverso le matrici potremo studiare esattamente come, quando e quante soluzioni ammette un sistema del genere, e perfino trovare un modo automatico per risolverli, senza doverci ragionare troppo.
Matrici
Rispondo subito alla prima e ovvia domanda: cos'è una matrice?
Osserviamo subito un esempio
come vedete, una matrice è semplicemente una griglia di valori reali.
Una matrice può avere un qualunque m >= 1 numero di righe e un qualunque n >= 1 numero di colonne.
Generalmente il nome delle matrici è una lettera maiuscola.
La notazione completa per riferirsi ad una matrice con m righe ed n colonne è , che nel nostro esempio diventa . Una matrice con n=m (come A) è detta quadrata.
Un'altra matrice particolare è quella che viene definita vettore colonna, che ha m > 1 righe ed una sola colonna.
Una matrice di questo tipo sostanzialmente non è diversa da un vettore di .
Per riferirsi allo specifico elemento di A nella i-esima riga e j-esima colonna si scrive . La matrice precedente può quindi essere riscritta come segue:
.
Tra le matrici sono definite le operazioni di:
- somma / differenza
- prodotto
Inoltre abbiamo anche l'operazione di:
- prodotto / divisione per uno scalare.
Partiamo dalla terza operazione. Moltiplicare (dividere) una matrice A per un numero k significa moltiplicare (dividere) ogni suo elemento per k... abbastanza intuibile, no?
(2.1)
Se volessi dividere A per h potrei semplicemente moltiplicarla per 1/h.
La somma tra due matrici, rigorosamente della stessa dimensione, segue la seguente regola:
(2.2)
La differenza tra due matrici segue dalla somma moltiplicando B per (-1)
(2.3)
Infine il prodotto tra due matrici, l'operazione che sicuramente ora come ora considererete la più difficile e/o astrusa, ma vi assicuro che se avete pazienza e andate avanti a leggere capirete perché è stato definito così.
Il prodotto tra due matrici viene chiamato prodotto righe per colonne , fondamentalmente perché da luogo ad una nuova matrice le cui componenti sono una somma di prodotto tra gli elementi di una riga di una matrice e di una colonna dell'altra.
Vale la seguente regola:
(2.4)
Posso spenderci qualche parola, ma comunque la cosa migliore è che proviate a inventare due matrici e a calcolarne la matrice prodotto per allenarvi a capire il meccanismo.
In pratica l'elemento i-j esimo di C si ottiene sommando i contributi di coppie di numeri moltiplicati tra loro.
Le coppie si scelgono muovendosi lungo la i-esima riga della matrice A (da sinistra verso destra) e lungo la j-esima colonna di B (dall'alto verso il basso). Questa definizione implica che non tutte le matrici possono essere moltiplicate tra loro, e che il prodotto tra due matrici cambia a seconda dell'ordine (A*B != B*A).
In particolare, affinché non restino valori scoppiati nella sommatoria precedente, è necessario che il numero di colonne di A (matrice a sinistra del prodotto) sia uguale al numero di righe di B (matrice a destra).
Consideriamo A(2,3) e B(3,1). Notiamo che il prodotto A*B è definito (si noti che B è una matrice "vettore colonna"),
e non solo B*A potrebbe essere diverso, ma in questo caso è addirittura indefinito, in quanto B ha 1 colonna mentre A ha 2 righe.
Non posso davvero rendervi le cose più semplici, è necessario che fate pratica voi.
Forza, prendete carta e matita e calcolatevi ogni singolo elemento di una matrice prodotto tra due matrici quadrate di terzo grado. Se non ci prendete la mano non possiamo andare avanti.
Riprendiamo in mano il sistema lineare precedente e costruiamone la matrice associata
Come vedete le componenti della matrice associata A sono ordinatamente i coefficienti delle variabili del sistema lineare.
Costruiamo il vettore colonna D
.
Chiamiamo X il vettore colonna che ha ordinatamente come componenti x1,x2,x3 (le variabili del sistema).
Adesso vi chiedo di espandere su carta, per esercizio, l'espressione matriciale
Per espandere intendo scrivere tutte le matrici in nella forma che mostra tutte le loro componenti. Ovviamente prima dovete calcolare la matrice risultate da A*X.
Fate un favore a voi stessi: impeditevi di leggere oltre finché non avete finito l'esercizio.
Se è andato tutto bene dovreste aver capito che
è un'altro modo di scrivere lo stesso sistema, ma molto più sintetico.
Se dovessimo imparare a maneggiare espressioni matriciali di questo tipo trovare la soluzione (le componenti della matrice vettore colonna X) potrebbe diventare una passeggiata.
Intanto proviamo a comportarci come se fosse un'espressione lineare. La soluzione si troverebbe moltiplicando ambo i membri per (ricordo che elevare un valore a (-1) equivale a farne il reciproco, ) . Otterremmo X = D/A.
In altre parole se riuscissimo a definire l'operatore di divisione tra matrici avremmo fatto il nostro lavoro.
Piccola nota: d'ora in poi considereremo matrici quadrate di ordine 3, che sono le più comuni per questi calcoli.
Introduciamo allora la matrice Identità, chiamata spesso I (Identity Matrix).
Questa è una matrice quadrata che ha tutti gli elementi della diagonale principale pari a 1, gli altri sono 0.
Inventate una qualunque matrice A3,3 e noterete che IA = AI = A.
Dimostriamolo:
notiamo che tutte le espressioni in cui si espande la sommatoria sono 0 eccetto quella in cui = 1, ovvero nell'unico caso in cui k=i.
La sommatoria si traduce quindi in:
concludiamo che B = A.
Potremmo considerare la matrice Identità in analogia con il numero reale 1 per le equazioni lineari:
1*a = a*1 = a;
Chiamiamo ora quella matrice che moltiplicata per A restituisce I.
Vorrei specificare bene che non è elevazione di A a (-1), è pura notazione, esattamente come non è ma la funzione inversa di f. Di fatto non esiste l'operazione di dividere qualcosa per una matrice, però per le sue caratteristiche possiamo immaginare (per capire meglio) che sia 1/A
(1/A * A farebbe, infatti, 1, che come ho detto prima può essere associato alla matrice identità I, ma di fatto 1 != I).
Osserviamo che per come la ho definita, deve avere dimensione 3,3. Infatti, essendo definiti entrambi i prodotti destro e sinistro di con A, deve avere lo stesso numero di colonne e di righe di A.
Sempre in analogia con i numeri reali possiamo notare che:
infatti a^(-1) = 1/a.
Moltiplicare una matrice per non significa proprio "dividerla" per A , ma è la cosa più vicina alla divisione che siamo riusciti a fare.
Riconsideriamo il sistema lineare precedente,
Moltiplicando sia a sinistra che a destra dell'uguale per una stessa matrice, l'uguaglianza rimane intatta:
Si noti che l'ordine dei prodotto nella prima espressione è stato mantenuto: è stata posizionata a sinistra di entrambe le parti.
L'ultima espressione mostra che per risolvere un sistema è sufficiente invertirne la matrice associata.
Trovare le componenti della matrice è un lavoro lungo e faticoso, adatto più ad un processore che all'uomo per la sua mole computazionale, ma una volta fatto consente di risolvere velocemente sistemi anche molto complessi e per questo è diventato il metodo più utilizzato per portare a termine le trasformazioni nei vertici in 3D.
è chiamata matrice inversa di A.
Cominciamo dicendo che non tutte le matrici sono invertibili, ovvero non tutte le matrici A ammettono tale che:
Per esempio, se il sistema cui la matrice A è associata fosse incompatibile, dovremmo pensare a
D come una matrice indefinita.
Visto che D è un vettore colonna costante, è proprio ad essere indefinita: si tratta di un caso in cui A non ammette inversa. Viceversa, se A ammette inversa, necessariamente D è soluzione del sistema .
Possiamo dire con certezza, quindi, che un sistema è compatibile se la sua matrice associata A ammette inversa.
Come ho già detto, invertire una matrice è un processo lungo e faticoso, e tale è anche la sua spiegazione.
A meno che non siate voi a chiedermi di farla, in questa sede non vi spiegherò come si invertono le matrici.
Tuttavia capisco che il discorso della compatibilità e delle dimensioni di un sistema possa comunque essere interessante, perciò possiamo focalizzarci su quello.
Consideriamo dunque , sempre in riferimento al sistema , la matrice
Essendo tale matrice uguale a D vale, per ogni riga i, la seguente uguaglianza:
Un sistema compatibile ammette la soluzione particolare [x1,x2,x3] che soddisfa le tre espressioni ottenute al variare di i da quella precedente.
Riscriviamo il sistema in una nuova forma
Potete verificare che si tratta della stessa cosa facendo uso delle proprietà 2.1 e 2.2 delle matrici (prodotto per numero reale e somma).
In questa forma però alcuni vettori chiave (che chiameremo A', B' ,C', e D' con ovvio significato dei nomi) sono stati isolati, inoltre appare chiaro che D' non è altro che una combinazione lineare dei vettori A' , B' , C'.
Una combinazione lineare di tre vettori restituisce un nuovo vettore che, in parole povere, "eredita" un tot dal primo, un tot dal secondo e un tot dal terzo.
Per spiegarmi meglio faccio un esempio. In precedenza abbiamo visto che un vettore può essere rappresentato come combinazione lineare dei vettori di una base
in
si ottiene prendendo volte , volte e volte .
In questo caso D' si ottiene prendendo x1 volte A', x2 volte B' e x3 volte C'.
Adesso, visto che D' potrebbe rappresentare un qualunque punto nello spazio, condizione sufficiente affinché un sistema sia compatibile è che i vettori colonna della sua matrice associata (ovvero A', B', C') non siano complanari.
Se lo fossero, infatti, non c'è santo che tenga: qualunque combinazione lineare di tre vettori complanari non potrà fare altro che originare un vettore che porta ad un punto ancora sullo stesso piano a cui appartengono i primi. Questo significa che se D' non appartiene anch'esso allo stesso piano, non esistono tre valori x1,x2,x3 che risolvano il sistema.
Ci resta ancora un problema, come verifichiamo che i vettori non siano complanari?
La risposta è semplice. Proviamo a costruire un parallelepipedo con i tre vettori che ci interessano, come quello in figura (ringrazio Wikipedia)
Si può dimostrare che il volume di questo solido equivale a fare il prodotto misto tra i tre vettori, indifferentemente dall'ordine con cui li moltiplichiamo, purché si tenga presente che il prodotto vettoriale è definito solo tra due matrici (non potete svolgere prima quello scalare, che restituisce un numero).
Quando il volume di questo solido è 0 significa che i vettori sono complanari.
Ad ogni matrice quadrata di terzo ordine possiamo attribuire un valore numerico che corrisponde al prodotto misto calcolato tra i suoi tre vettori colonna. Questo numero si chiama determinante della matrice considerata.
Se il determinante della matrice A associata ad un sistema è nullo, il sistema risulta incompatibile.
Peraltro per calcolare la matrice inversa , tra le altre operazioni, la matrice A viene divisa per il suo determinante, e sappiamo che dividere per 0 è indefinito.
[/color]