Summary: Elementi di composizione grafica 2D e 3D (curve incluse) per l'ambiente e linguaggio Processing
Note: Your browser may not currently support MathML. See our browser support page for additional details. You can always view the correct math in the PDF version.
In Processing si possono disporre punti, linee, superfici, e volumi (cioè oggetti geometrici a 0, 1, 2, o 3 dimensioni) in uno spazio tridimensionale ovvero, laddove la cosa abbia un senso, nello spazio bidimensionale della finestra immagine. Il numero di parametri delle primitive corrispondenti determinerà se tali oggetti debbano essere collocati nello spazio X-Y o nello spazio X-Y-Z.
La point() è la più semplice
delle primitive grafiche. Se invocata con due coordinate,
colloca un punto nello spazio X-Y. Se invocata con tre
coordinate, colloca il punto nello spazio X-Y-Z. Un punto, in
senso geometrico, non ha dimensione, ma ad esso può essere
associata una occupazione in pixel ed un colore, mediante le
funzioni strokeWeight() e stroke(),
rispettivamente. Ad esempio, il semplicissimo sketch Processing
stroke(180,0,0);
strokeWeight(10);
point(60,60);
Un insieme di punti può essere raggruppato in un unico
oggetto (nuvola di punti) mediante i due delimitatori
beginShape(POINTS) e endShape(),
tra i quali ogni punto va specificato con la funzione
vertex(). Trasformazioni di rotazione,
traslazione, o scalamento non si applicano internamente agli
oggetti compositi descritti da beginShape() e
endShape(), ma possono precedere la definizione
di un composito e applicarsi all'insieme.
La
line() traccia un segmento di retta tra due punti
del piano o dello spazio, con spessore e colore eventualmente
impostati tramite strokeWeight() e
stroke(), rispettivamente.
Un insieme di segmenti può essere definito,
analogamente a quanto visto per i punti, mediante i
delimitatori beginShape() e
endShape(), tra i quali si elencano gli estremi
dei segmenti mediante la funzione vertex(). Usando
la invocazione beginShape(LINES) i vertici vengono
presi a coppie, ciascuna identificante un segmento, mentre con
la invocazione senza argomenti beginShape() i vertici,
presi uno dopo l'altro, definiscono una spezzata. Chiudendo con
endShape(CLOSE) la spezzata
viene chiusa su se stessa mediante collegamento del primo e
dell'ultimo vertice. Il colore del poligono così ottenuto può essere impostato mediante la fill(), ovvero lasciato uguale a quello dello sfondo con la noFill().
La funzione
curve(), invocata con otto parametri, traccia una
curva sul piano immagine, con punto iniziale e finale
determinati, rispettivamente, dalle seconda e dalla terza
coppia di coordinate passate come argomenti. Le due coppie di
coordinate iniziale e finale definiscono due punti di
controllo della curva tracciata, la quale è una spline
interpolante che passa per tutti e quattro i punti. In Processing, tuttavia, viene visualizzato solo il segmento di curva compreso tra i due punti intermedi.
curveVertex() per specificare
ciascun punto all'interno di un blocco delimitato da
beginShape() e
endShape().
A differenza della
curve(), nella funzione bezier() i
due punti di controllo specificati dai quattro parametri
intermedi non sono punti attraversati dalla curva. Essi
servono solo a definire la forma della curva approssimante
di Bézier,
la quale ha le seguenti proprietà notevoli:
stroke(255, 0, 0);
line(93, 40, 10, 10);
line(90, 90, 15, 80);
stroke(0, 0, 0);
noFill();
bezier(93, 40, 10, 10, 90, 90, 15, 80);
bezierVertex() per specificare ciascun punto
all'interno di un blocco delimitato da
beginShape() e
endShape(). In questo modo si può tracciare una
curva arbitrariamente involuta in uno spazio a tre
dimensioni. In due dimensioni, la
bezierVertex() ha sei parametri che
corrispondono alle coordinate di due punti di controllo e di
un punto di ancoraggio. La prima invocazione di
bezierVertex() va preceduta da una invocazione
di vertex() che stabilisce il primo punto di
ancoraggio della curva.
Ci sono ulteriori metodi che consentono di leggere le
coordinate o la direzione della tangente in un punto
qualsiasi lungo la curva di Bézier o spline interpolante,
stabilito mediante un parametro
Lo sketch processing di tabella illustra la differenza tra curva interpolante spline e curva di Bézier.
| applet che confronta curva di Bézier (rossa) e spline interpolante (nera) |
|
fill(), il quale prevede anche la possibilità di
impostare la trasparenza. Il triangolo è
l'elemento costruttivo fondamentale per la grafica 3D, in
quanto mediante giustapposizione di triangoli si approssimano
superfici continue. In Processing, però, i triangoli sono
elementi specificati nel 2D mediante la primitiva
triangle(), la quale ha sei parametri
corrispondenti alle coordinate dei tre vertici nella finestra
immagine. Pur essendo definito nel 2D, ogni triangolo può
essere soggetto a trasformazioni di rotazione e traslazione
all'interno dello spazio 3D, come avviene nello sketch
Processing
void setup(){
size(200, 200, P3D);
fill(210, 20, 20);
}
float angle = 0;
void draw(){
background(200); // clear image
stroke(0,0,0);
angle += 0.005;
rotateX(angle);
triangle(10,10,30,70,80,80);
}
Un insieme di triangoli può essere definito,
analogamente a quanto visto per i punti e segmenti, mediante i
delimitatori beginShape() e
endShape(), tra i quali si elencano i vertici dei
triangoli mediante la funzione vertex(). Usando
la invocazione beginShape(TRIANGLES) i vertici
vengono presi a terne, ciascuna identificante un triangolo,
mentre con la invocazione
beginShape(TRIANGLE_STRIP) i vertici, presi uno
dopo l'altro, definiscono una striscia di superficie a facce
triangolari. Se le vertex() hanno tre argomenti,
i vertici vengono collocati nello spazio 3D e i corrispondenti
triangoli individuano superfici planari nello spazio.
I rettangoli
si definiscono, in Processing, mediante la funzione
rect() di quattro parametri, in cui i primi due
specificano, per default, la posizione nel piano 2D
dell'angolo in alto a sinistra, e il terzo e quarto
specificano, rispettivamente, la larghezza e la altezza. Il
significato dei primi due parametri si può cambiare con la
funzione rectMode():
rectMode(CORNER) corrisponde al posizionamento
di default; rectMode(CENTER)
corrisponde al posizionamento del centro del rettangolo nel
punto specificato dalle prime due coordinate;
rectMode(CORNERS) fa in modo che i quattro
parametri vengano interpretati come le coordinate del
vertice in alto a sinistra e del vertice in basso a destra.
Un quadrilatero generico si definisce mediante le coordinate
dei suoi quattro vertici, passate come parametri alla
funzione quad().
E' importante notare che in 3D, mentre un triangolo rimane
in ogni caso planare, una quadrupla di punti può dare luogo
ad una superficie curva. Viceversa, i quadrilateri definiti
mediante roto-traslazioni 3D di quadruple di vertici 2D
rimangono planari. Processing permette di passare solo otto
parametri alla quad(), in tal modo forzando la
definizione del quadrilatero mediante una quadrupla di
vertici 2D.
Un insieme di quadrilateri può essere
definito, analogamente a quanto visto per i triangoli,
mediante i delimitatori beginShape() e
endShape(), tra i quali si elencano i vertici dei
quadrilateri mediante la funzione
vertex(). Usando la invocazione
beginShape(QUADS) i vertici vengono presi a
quadruple, ciascuna identificante un quadrilatero, mentre con
la invocazione beginShape(QUAD_STRIP) i vertici,
presi uno dopo l'altro, definiscono una striscia di superficie
a facce quadrilatere. Se i vertex() usati hanno
tre parametri, non è garantita la planarità delle facce
risultanti, e quindi la resa grafica può essere fuorviante. Ad esempio, eseguendo il codice
size(200,200,P3D);
lights();
beginShape(QUADS);
vertex(20,31, 33);
vertex(80, 40, 38);
vertex(75, 88, 50);
vertex(49, 85, 74);
endShape();
Un poligono generico
è definito da una successione di vertici, ed è un oggetto
dotato di una superficie che può essere colorata. In
Processing tali vertici si elencano dentro a una coppia
beginShape(POLYGON); - endShape();
In realtà il poligono è da intendersi in senso
generalizzato, essendo possibile usare le
bezierVertex() e curveVertex() per
specificare profili curvi. Ad esempio, si provi a disegnare la
luna:
fill(246, 168, 20);
beginShape(POLYGON);
vertex(30, 20);
bezierVertex(80, 10, 80, 75, 30, 75);
bezierVertex(50, 70, 60, 25, 30, 20);
endShape();
La funzione
ellipse() traccia una ellisse nel piano 2D. I
quattro parametri sono interpretati, come nel caso della
rect(), come posizione seguita da larghezza e
altezza. La posizione può essere regolata mediante la chiamata
ellipseMode(), il cui parametro può assumere
valori CORNER, CORNERS,
CENTER, CENTER_RADIUS. I primi due di questi
quattro possibili valori vanno riferiti al rettangolo
circoscritto alla ellisse.
Processing offre un repertorio assai limitato di primitive di oggetti tridimensionali, essenzialmente solo sfere e parallelepipedi.
La funzione
box() produce un cubo se invocata con un solo
parametro (lato), un parallelepipedo se invocata con tre
parametri (larghezza, altezza, profondità).
La funzione
sphere() produce, mediante un poliedro
approssimante, una sfera il cui raggio è specificato come
parametro. La funzione sphereDetail() può essere
usata per specificare il numero di vertici del poliedro che
approssima la sfera ideale.
Una rotazione o una traslazione possono essere pensate come
operazioni che ruotano o traslano il sistema di riferimento
cartesiano. In altri termini, dopo una rotate() o
una translate() le successive operazioni di
posizionamento di oggetti grafici avranno un nuovo sistema di
assi coordinati. Quando si posizionano diversi oggetti in vario
modo nello spazio, è utile tenere traccia dei diversi sistemi di
assi coordinati che via via si vanno ad impostare. La struttura
dati atta a contenere tali sistemi è il cosiddetto stack (o
pila) delle trasformazioni (matrix
stack). Con la funzione pushMatrix() il
sistema di coordinate corrente viene impilato in testa allo
stack. Invece, per recuperare il sistema di coordinate
precedente alla ultima trasformazione operata, bisogna operare
una popMatrix(). Di fatto, lo stack contiene le matrici
di trasformazione affine, secondo quanto prescritto da OpenGL e descritto in Sezione 14.
In questo esempio vengono collocati due oggetti, uno quadrato planare
ed un cubo, nello spazio 3D. Il primo pushMatrix()
salva sullo stack il sistema di coordinate, quindi vengono
applicate alcune trasformazioni che precedono il disegno del
quadrato. Per tornare al sistema di coordinate precedente, e
quindi operare nuove trasformazioni per collocare il cubo, si
applica una popMatrix(). Di fatto, le
pushMatrix() e popMatrix() delimitano lo scope
relativo al posizionamento geometrico di un oggetto.
float angle;
void setup(){
size(100, 100, P3D);
angle = 0;
}
void draw(){
background(200);
angle += 0.003;
pushMatrix();
translate(25,50);
rotateZ(angle);
rotateY(angle);
rectMode(CENTER);
rect(0,0,20,20);
popMatrix();
translate(75,50,-25);
rotateX(angle);
box(20);
}
Il modello di illuminazione di Processing ricalca direttamente quello di OpenGL, cioè lo shading di Phong. Tale modello non è giustificato fisicamente, ma è particolarmente efficiente. OpenGL considera illuminato ogni poligono la cui normale forma un angolo acuto con la direzione di provenienza della luce. Questo a prescindere dalla presenza di oggetti mascheranti, e quindi le ombre non vengono rese. Si dice che OpenGL adotta un modello locale di illuminazione, in quanto le riflessioni multiple tra le superfici e le proiezioni di ombre non vengono automaticamente elaborate.
E' disponibile una sorgente di luce di
ambiente, cioè non proveniente da alcuna direzione particolare,
il cui colore viene specificato mediante i parametri della
chiamata di attivazione ambientLight(). Una
sorgente di luce direzionale viene impostata con
directionalLight(), i cui parametri specificano
colore e direzione di provenienza. Il metodo
lights() attiva una combinazione di default di luce
ambientale grigia e luce direzionale, anch'essa grigia,
proveniente dalla direzione frontale. E' possibile impostare una
sorgente luminosa puntiforme in una data posizione dello spazio,
mediante la chiamata pointLight(). Infine, il
metodo spotLight() attiva un fascio di luce di cui,
oltre a colore, posizione e direzione di irraggiamento, è
possibile controllare l'apertura dell'angolo e la concentrazione
intorno all'asse, mediante l'esponente
Considerata una superficie piana, una luce direzionale che
incida su di essa produce luce riflessa secondo varie direzioni,
in maniera dipendente dalle proprietà superficiali. Nel caso di
riflessione perfettamente diffusiva (o
lambertiana), l'irraggiamento è prodotto in tutte
le direzioni allo stesso modo, con una intensità tanto maggiore
quanto più la direzione di incidenza è vicina alla direzione
ortogonale (o normale) alla superficie. Viceversa, se la
superficie è perfettamente riflettente, la luce viene riflessa
solo lungo la direzione simmetrica, rispetto alla normale, alla
direzione di incidenza. In OpenGL, per avere la massima
flessibilità nella definizione della illuminazione, ogni
sorgente racchiude in sé le tre componenti di illuminazione
ambientale, lambertiana, e speculare. Queste tre componenti sono
definibili separatamente, e interagiscono con le rispettive tre
componenti che definiscono le proprietà superficiali degli
oggetti. I colori definiti nei parametri dei metodi
directionalLight(), pointLight(), e
spotLight() definiscono la componente lambertiana
di illuminazione. La lightSpecular() specifica il
colore della componente di luce incidente che viene trattata con
riflessione speculare.
In Processing è possibile controllare le proprietà delle
superfici mediante i metodi ambient() (che agisce
sulla componente ambientale di luce) e specular()
(che agisce sulla componente speculare). La shininess() controlla la
concentrazione del fascio riflesso specularmente, mediante un
coefficiente che agisce in modo analogo all'esponente della
Equazione 1. Gli oggetti rappresentati possono
anche essere considerati sorgenti di luce, e quindi si può
assegnare loro una luce di emissione, mediante la
emmissive(). Tuttavia, le sorgenti definite in
questo modo non producono illuminazione sugli altri oggetti
della scena.
In OpenGL, le luci puntiformi, a spot, e ambientali subiscono una attenuazione dipendente dalla distanza, secondo il modello
ligthFalloff() consente di
specificare i parametri Qui un cubo e una QUAD_STRIP
vengono collocati nello spazio ed illuminati da una sorgente
rotante. Inoltre, viene impostate una tenue luce fissa. Si
noti l'assenza di ombre e la apparente planarità delle
superfici della QUAD_STRIP.
float r;
float lightX, lightY, lightZ;
void setup() {
size(400, 400, P3D);
r = 0;
ambient(180, 90, 0);
specular(0, 0, 240);
lightSpecular(200, 200, 200);
shininess(5);
}
void draw() {
lightX = 100*sin(r/3) + width/2;
lightY = 100*cos(r/3) + height/2;
lightZ = 100*cos(r);
background(0,0,0);
noStroke();
ambientLight(153, 102, 0);
lightSpecular(0, 100, 200);
pointLight(100, 180, 180,
lightX, lightY, lightZ);
pushMatrix();
translate(lightX, lightY, lightZ);
emissive(100, 180, 180);
sphere(4); //Put a little sphere where the light is
emissive(0,0,0);
popMatrix();
pushMatrix();
translate(width/2, height/2, 0);
rotateX(PI/4);
rotateY(PI/4);
box(100);
popMatrix();
pushMatrix();
translate(width/4, height/2, 0);
beginShape(QUAD_STRIP);
vertex(10,13,8);
vertex(13,90,13);
vertex(65,76,44);
vertex(95,106,44);
vertex(97,20,70);
vertex(109,70,80);
endShape();
popMatrix();
r+=0.05;
}
Una
proiezione prospettica è caratterizzata da un centro di
proiezione e da un piano di proiezione. I
raggi proiettori collegano i punti della scena
con il centro di proiezione, individuando i corrispondenti
punti proiettati sul piano di proiezione. La Figura 1 mostra una vista in sezione in cui il
piano di proiezione è individuato da una retta di ascissa
![]() |
Più in generale, la proiezione di un punto di coordinate omogenee
Le viste parallele sono quelle che si ottengono facendo
retrocedere a infinito (
La proiezione ortografica produce una classe di viste
parallele mediante conduzione di proiettori perpendicolari
al piano di proiezione. Se il piano di proiezione viene
posto perpendicolare all'asse
Si parla di proiezione obliqua ogniqualvolta i raggi proiettori
sono obliqui (non perpendicolari) rispetto al piano di
proiezione. Per introdurre una deviazione dei raggi
proiettori dalla perpendicolare secondo angoli
Come abbiamo visto, Processing offre un modello di illuminazione locale, e quindi non è possibile proiettare ombre (shadow casting) per via diretta. Tuttavia, la manipolazione delle matrici di trasformazione affine ci consente di proiettare abbastanza agevolmente ombre su piani. La tecnica che si usa è detta flashing in the eye, con questo termine intendendo che si sposta il centro ottico della scena nel punto in cui è collocata la sorgente luminosa, e si procede con una trasformazione prospettica con piano di proiezione coincidente con quello su cui si vuole proiettare l'ombra.
Il codice seguente proietta sul pavimento l'ombra generata
da una sorgente di luce collocata sull'asse
| Proiezione di un'ombra |
|---|
![]() |
size(200, 200, P3D);
float centro = 100;
float yp = 70; // distanza del piano di proiezione (il pavimento) dal centro
float yl = 40; // altezza del centro di proiezione (la sorgente di luce)
// rispetto al centro
translate(centro, centro, 0); // spostamento dell'origine
// al centro della finestra
noFill();
box(yp*2); //disegno del cubo-stanza
pushMatrix();
fill(250); noStroke();
translate(0, -yl, 0); // spostamento in alto (rispetto al centro)
// della "lampadina"
sphere(4); // disegno della sfera che rappresenta la lampadina
stroke(10);
popMatrix();
pushMatrix(); // disegno del cubo wireframe
noFill();
rotateY(PI/4); rotateX(PI/3);
box(20);
popMatrix();
// PROIEZIONE DELL'OMBRA MEDIANTE
// COMPOSIZIONE DI TRE TRASFORMAZIONI (la terza è applicata per prima)
translate(0, -yl, 0); // riporta sorgente di luce e pavimento
// nelle rispettive posizioni (vedi la traslazione
// sotto che viene applicata per prima all'ombra)
applyMatrix(1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 1/(yp+yl), 0, 0); // proiezione sul pavimento
// spostato in basso di yl
translate(0, yl, 0); // spostamento della sorgente di luce
// (il centro di proiezione) al centro
// della stanza (ovvero nell'origine)
// e, solidalmente, del "pavimento" in basso di yl
pushMatrix(); // disegno del cubo grigio che genera l'ombra
fill(120, 50); // mediante le trasformazioni di cui sopra
noStroke();
rotateY(PI/4); rotateX(PI/3);
box(20);
popMatrix();
OpenGL è un insieme di funzioni che consentono al programmatore di accedere al sistema grafico. In gergo tecnico è una Application Programming Interface (API). Principalmente essa si occupa della resa grafica (rendering) di una scena popolata di oggetti tridimensionali e luci, da un certo punto di vista. Per quanto riguarda il programmatore, OpenGL consente di descrivere oggetti geometrici ed alcune loro proprietà, nonché di stabilire come tali oggetti debbano essere illuminati e visti. Dal punto di vista realizzativo, OpenGL è basato su una pipeline grafica composta di moduli, come riportato in Figura 3. Un ottimo libro di grafica interattiva in OpenGL è quello di Angel.
| La pipeline di OpenGL |
|---|
![]() |
In Processing (ed in OpenGL),
l'utente specifica gli oggetti mediante coordinate mondo
(standard coordinates). La
model-view matrix è la matrice di
trasformazione usata per passare da coordinate mondo a uno
spazio associato alla camera da presa. Ciò consente di
cambiare dinamicamente il punto di vista e l'orientazione
della camera. In OpenGL ciò avviene mediante la funzione
gluLookAt(), la quale è riprodotta in Processing
dalla camera(). I primi tre parametri
identificano la posizione, in coordinate mondo, del centro
ottico della camera (eye point). La seconda terna
di parametri identifica un punto al quale è rivolta la camera
(center of the scene). La terza terna di
coordinate identifica un vettore atto a specificare la
verticale di ripresa. Ad esempio, il codice
void setup() {
size(100, 100, P3D);
noFill();
frameRate(20);
}
void draw() {
background(204);
camera(70.0, 35.0, 120.0, 50.0, 50.0, 0.0,
(float)mouseX /width, (float)mouseY /height, 0.0);
translate(50, 50, 0);
rotateX(-PI/6);
rotateY(PI/3);
box(45);
}
La projection matrix si occupa di fare la
proiezione sulla finestra di vista, proiezione che può essere
parallela (o ortografica) o prospettica. La proiezione
ortografica si può attivare con la chiamata
ortho(). La proiezione prospettica è quella di
default, ma si può esplicitamente attivare con
perspective(). Proiezioni particolari, quali
quelle oblique, possono essere ottenute per distorsione degli
oggetti mediante applicazione della
applyMatrix(). C'è inoltre la texture
matrix, di cui ci si occupa in un altro modulo.
Per ogni tipo di matrice, OpenGL mantiene uno stack di
matrici, la matrice corrente essendo quella in cima. Il meccanismo
dello stack consente di salvare uno stato (mediante
pushMatrix()) prima di operare nuove trasformazioni, o di
rimuovere lo stato corrente evidenziando uno stato precedente
(mediante popMatrix()). Ciò si riflette nelle operazioni
Processing descritte in Sezione 6. Le trasformazioni in
OpenGL sono applicate secondo lo schema:
Un viewport è un'area rettangolare
della finestra di display. Il passaggio dal piano di proiezione
prospettica al viewport avviene in due passi: (i)
trasformazione in una finestra 2 x 2 centrata nell'origine
( normalized device coordinates ) (ii)
mappatura della finestra normalizzata sul viewport. Usando la
rappresentazione intermedia a coordinate normalizzate,
l'operazione di clipping, cioè di eliminazione
degli oggetti o loro parti non visibili attraverso la finestra,
diventa banale. In Processing, le funzioni
screenX(), screenY(), e
screenZ() consentono di ottenere le coordinate X-Y
prodotte dalla trasformazione di viewport e dagli elementi
precedenti della catena di Figura 3.
Il frustum di vista è l'angolo
solido attraverso il quale si attua la proiezione prospettica,
come indicato in Figura 4. Vengono visualizzati
gli oggetti (o le loro parti) presenti nel volume di vista, le
rimanenti parti essendo soggette a clipping. In Processing (e in
OpenGL) il frustum si può definire mediante posizionamento dei
sei piani che lo individuano (frustum()), oppure
mediante specificazione di angolo verticale, aspect
ratio, posizione dei piani anteriore e posteriore
(perspective()). Ci si può chiedere come avvenga
la rimozione delle facce nascoste, cioè di quelle facce che sono
mascherate da altre facce presenti nel volume di vista. OpenGL
usa l'algoritmo dello z-buffer, che
è supportato dalle schede grafiche. La scheda tiene un'area di
memoria bidimensionale (lo z-buffer) corrispondente ai pixel
della finestra di vista e contenente valori di profondità. Prima
di proiettare un poligono sulla finestra di vista la scheda va a
controllare che i pixel affetti da tale poligono non abbiano già
un valore di profondità inferiore rispetto a quello del poligono
in oggetto. In questo caso significherebbe che c'è un oggetto
grafico che maschera quello che si sta disegnando.
| Il frustum di vista |
|---|
![]() |
Elaborazioni geometriche sofisticate si possono ottenere
manipolando direttamente le matrici di proiezione e
model-view. Ciò è possibile, in Processing, a partire dalla
matrice unitaria, caricata con resetMatrix(),
mediante moltiplicazioni matriciali eseguite con
applyMatrix().
Si esegua e si analizzi il codice Processing
size(200, 200, P3D);
println("Default matrix:"); printMatrix();
noFill();
ortho(-width/2, width/2, -height/2, height/2, -100, 100);
translate(100, 100, 0);
println("After translation:"); printMatrix();
rotateX(atan(1/sqrt(2)));
println("After about-X rotation:"); printMatrix();
rotateY(PI/4);
println("After about-Y rotation:"); printMatrix();
box(100);
Il wireframe di un cubo viene visualizzato in assonometria
isometrica. Le ultime tre matrici rappresentano,
accumulandole una dopo l'altra, le tre operazioni di
traslazione (per allineare il cubo al centro della
finestra), rotazione intorno all'asse
Si scriva un codice Processing che produca la proiezione obliqua di un cubo.
Ad esempio:
size(200, 200, P3D);
float theta = PI/6;
float phi = PI/12;
noFill();
ortho(-width/2, width/2, -height/2, height/2, -100, 100);
translate(100, 100, 0);
applyMatrix(1, 0, - tan(theta), 0,
0, 1, - tan(phi), 0,
0, 0, 0, 0,
0, 0, 0, 1);
box(100);
Si visualizzi un cubo che proietti la sua ombra sul suolo, assumendo che la sorgente luminosa sia a distanza infinita (quale si può considerare, in pratica, la distanza del sole).
Si opera in modo simile a quanto visto in Esempio 3, ma la trasformazione proiettiva è di tipo ortografico:
size(200, 200, P3D);
noFill();
translate(100, 100, 0);
pushMatrix();
rotateY(PI/4); rotateX(PI/3);
box(30);
popMatrix();
translate(0, 60, 0); //proietta un'ombra da infinito (sole)
applyMatrix(1, 0, 0, 0,
0, 0, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1);
fill(150);
pushMatrix();
noStroke();
rotateY(PI/4); rotateX(PI/3);
box(30);
popMatrix();