Uso dei triangoli per ottenere effetti tridimensionali

Flash Player 10 e versioni successive, Adobe AIR 1.5 e versioni successive

In ActionScript, le trasformazioni bitmap vengono effettuate mediante il metodo Graphics.drawTriangles(), poiché i modelli 3D sono rappresentati da una raccolta di triangoli nello spazio. (Tuttavia, poiché Flash Player e AIR non supportano un buffer di profondità, gli oggetti di visualizzazione continuano a essere intrinsecamente piatti, o bidimensionali. Questa caratteristica è descritta in Nozioni fondamentali sugli oggetti di visualizzazione 3D in Flash Player e nel runtime AIR.) Il metodo Graphics.drawTriangles() è simile al metodo Graphics.drawPath(), dal momento che utilizza un gruppo di coordinate per disegnare un tracciato di triangoli.

Per acquisire dimestichezza con l'uso di Graphics.drawPath(), vedete Disegno di tracciati.

Il metodo Graphics.drawTriangles() utilizza un metodo Vector.<numero> per specificare le posizioni dei punti del tracciato di triangoli:

drawTriangles(vertices:Vector.<Number>, indices:Vector.<int> = null, uvtData:Vector.<Number> = null, culling:String = "none"):void

Il primo parametro di drawTriangles() è vertices ed è l'unico obbligatorio. Si tratta di un vettore di numeri che definiscono le coordinate mediante cui i triangoli vengono disegnati. Un gruppo di tre serie di coordinate (sei numeri) rappresenta un tracciato di triangoli. Senza il parametro indices, la lunghezza del vettore dovrebbe essere sempre un fattore di sei elementi, dal momento che ogni triangolo richiede tre coppie di coordinate (tre gruppi di due valori x/y). Ad esempio:

graphics.beginFill(0xFF8000); 
graphics.drawTriangles( 
    Vector.<Number>([ 
        10,10,  100,10,  10,100, 
        110,10, 110,100, 20,100]));

Nessuno di questi triangoli condivide dei punti ma, se così fosse, il secondo parametro di drawTriangles() (indices) potrebbe essere usato per riutilizzare i valori nel vettore vertices per più di un triangolo.

Quando usate il parametro indices, è importante che siate consapevoli che i valori indices sono indici di punto, e non indici che fanno riferimento direttamente agli elementi dell'array vertices. In altre parole, un indice nel vettore vertices definito da indices è di fatto l'indice reale diviso per 2. Per il terzo punto di un vettore vertices, usate ad esempio il valore 2 per indices, anche se il primo valore numerico di tale punto inizia in corrispondenza dell'indice 4 del vettore.

Ad esempio, unite due triangoli per condividere il bordo diagonale mediante il parametro indices:

graphics.beginFill(0xFF8000); 
graphics.drawTriangles( 
    Vector.<Number>([10,10, 100,10, 10,100, 100,100]), 
    Vector.<int>([0,1,2, 1,3,2]));

Anche se a questo punto è stato disegnato un quadrato con i due triangoli, solo quattro punti sono stati specificati nel vettore vertices. Mediante indices, i due punti condivisi dai due triangoli vengono riutilizzati per ciascun triangolo. In questo modo si riduce il numero totale di vertici da 6 (12 numeri) a 4 (8 numeri):

Un quadrato disegnato con due triangoli mediante il parametro vertices

Questa tecnica è particolarmente utile con le trame di triangoli di grandi dimensioni in cui la maggior parte dei punti è condivisa da più triangoli.

Tutti i riempimenti possono essere applicati ai triangoli. I riempimenti vengono applicati alla trama di triangoli risultante come accade con qualunque altra forma.

Trasformazione delle bitmap

Le trasformazioni delle bitmap danno l'illusione della prospettiva o di una “texture” su un oggetto tridimensionale. Nello specifico, potete applicare una distorsione a una bitmap verso un fuoco prospettico in modo che l'immagine sembri restringersi man mano che si sposta verso di esso. Oppure, potete utilizzare una bitmap bidimensionale per creare una superficie per un oggetto tridimensionale che dia l'impressione di una texture applicata su tale oggetto.

Una superficie bidimensionale ottenuta mediante un fuoco prospettico e un oggetto tridimensionale con texture mediante una bitmap

Mappatura UV

Una volta iniziato a usare le texture, potete utilizzare il parametro uvtData di drawTriangles(), che consente di impostare la mappatura UV per i riempimenti bitmap.

La mappatura UV è un metodo di applicazione delle texture agli oggetti che utilizza due valori: un valore orizzontale U (x) e un valore verticale V (y). Anziché essere basati su valori di pixel, si basano su percentuali. Le coordinate 0 U e 0 V corrispondono alla parte superiore sinistra di un'immagine, mentre le coordinate 1 U e 1 V corrispondono alla parte inferiore destra:

Le posizioni UV 0 e 1 su un'immagine bitmap

Potete assegnare delle coordinate UV ai vettori di un triangolo per associare questi ultimi alle rispettive posizioni su un'immagine:

Le coordinate UV di un'area triangolare di un'immagine bitmap

I valori UV rimangono coerenti con i punti del triangolo:

Quando i vertici del triangolo si spostano, la bitmap viene distorta per mantenere uguali i valori UV di un singolo punto

Man mano che vengono applicate delle trasformazioni 3D ActionScript al triangolo associato alla bitmap, l'immagine bitmap viene applicata al triangolo basato sui valori UV. Quindi, anziché utilizzare i calcoli di matrice, potete impostare o regolare i valori UV per creare un effetto tridimensionale.

Il metodo Graphics.drawTriangles() accetta anche un'informazione opzionale per le trasformazioni tridimensionali: il valore T. Il valore T in uvtData rappresenta la prospettiva tridimensionale o, più specificamente, il fattore di scala del vertice associato. La mappatura UVT aggiunge la correzione prospettica alla mappatura UV. Ad esempio, se all'interno dello spazio tridimensionale un oggetto è posizionato lontano dal punto di vista in modo da apparire al 50% della sua dimensione “originale”, il valore T di tale oggetto è 0,5. Dal momento che i triangoli vengono disegnati per rappresentare gli oggetti nello spazio tridimensionale, le loro posizioni lungo l'asse z determinano i loro valori T. L'equazione che determina il valore T è:

T = focalLength/(focalLength + z);
In questa equazione, focalLength rappresenta una distanza focale o una posizione sullo schermo calcolata che specifica la quantità di prospettiva fornita nella visualizzazione.
La distanza focale e il valore z
A.
Punto di vista

B.
Schermo

C.
Oggetto 3D

D.
Valore focalLength

E.
Valore z

Il valore T viene utilizzato per modificare in scala le forme base e farle sembrare più lontane. Si tratta di solito del valore utilizzato per convertire i punti 3D in punti 2D. Nel caso dei dati UVT, viene utilizzato anche per modificare in scala una bitmap tra i punti all'interno di un triangolo con prospettiva.

Quando definite i valori UVT, il valore T segue direttamente i valori UV definiti per un vertice. Con l'inclusione di T, ogni serie di tre valori nel parametro uvtData (U, V e T) corrisponde a una coppia di valori nel parametro vertices (x e y). Con solo i valori UV, uvtData.length == vertices.length. Con l'inclusione di un valore T, uvtData.length = 1.5*vertices.length.

L'esempio seguente mostra un piano che viene ruotato nello spazio tridimensionale mediante i dati UVT. Nell'esempio viene utilizzata un'immagine di nome ocean.jpg e una classe di tipo helper, ImageLoader, per caricare l'immagine ocean.jpg per poterla assegnare all'oggetto BitmapData.

Ecco l'origine della classe ImageLoader (salvate questo codice in un file denominato ImageLoader.as):

package { 
    import flash.display.* 
    import flash.events.*; 
    import flash.net.URLRequest; 
    public class ImageLoader extends Sprite { 
        public var url:String; 
        public var bitmap:Bitmap; 
    public function ImageLoader(loc:String = null) { 
        if (loc != null){ 
            url = loc; 
            loadImage(); 
        } 
    } 
    public function loadImage():void{ 
        if (url != null){ 
            var loader:Loader = new Loader(); 
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onComplete); 
            loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onIoError); 
             
                var req:URLRequest = new URLRequest(url); 
                loader.load(req); 
            } 
        } 
         
    private function onComplete(event:Event):void { 
            var loader:Loader = Loader(event.target.loader); 
            var info:LoaderInfo = LoaderInfo(loader.contentLoaderInfo); 
            this.bitmap = info.content as Bitmap; 
            this.dispatchEvent(new Event(Event.COMPLETE)); 
    } 
         
    private function onIoError(event:IOErrorEvent):void { 
            trace("onIoError: " + event); 
    } 
    } 
}

Ed ecco il codice ActionScript che utilizza i triangoli, la mappatura UV e i valori T per far sembrare che l'immagine si restringa mentre si muove verso un fuoco prospettico e ruota. Salvate questo codice in un file di nome Spinning3dOcean.as:

package { 
    import flash.display.* 
    import flash.events.*; 
    import flash.utils.getTimer; 
     
    public class Spinning3dOcean extends Sprite { 
        // plane vertex coordinates (and t values) 
        var x1:Number = -100,    y1:Number = -100,    z1:Number = 0,    t1:Number = 0; 
        var x2:Number = 100,    y2:Number = -100,    z2:Number = 0,    t2:Number = 0; 
        var x3:Number = 100,    y3:Number = 100,    z3:Number = 0,    t3:Number = 0; 
        var x4:Number = -100,    y4:Number = 100,    z4:Number = 0,    t4:Number = 0; 
        var focalLength:Number = 200;      
        // 2 triangles for 1 plane, indices will always be the same 
        var indices:Vector.<int>; 
         
        var container:Sprite; 
         
        var bitmapData:BitmapData; // texture 
        var imageLoader:ImageLoader; 
        public function Spinning3dOcean():void { 
            indices =  new Vector.<int>(); 
            indices.push(0,1,3, 1,2,3); 
             
            container = new Sprite(); // container to draw triangles in 
            container.x = 200; 
            container.y = 200; 
            addChild(container); 
             
            imageLoader = new ImageLoader("ocean.jpg"); 
            imageLoader.addEventListener(Event.COMPLETE, onImageLoaded); 
        } 
        function onImageLoaded(event:Event):void { 
            bitmapData = imageLoader.bitmap.bitmapData; 
            // animate every frame 
            addEventListener(Event.ENTER_FRAME, rotatePlane); 
        } 
        function rotatePlane(event:Event):void { 
            // rotate vertices over time 
            var ticker = getTimer()/400; 
            z2 = z3 = -(z1 = z4 = 100*Math.sin(ticker)); 
            x2 = x3 = -(x1 = x4 = 100*Math.cos(ticker)); 
             
            // calculate t values 
            t1 = focalLength/(focalLength + z1); 
            t2 = focalLength/(focalLength + z2); 
            t3 = focalLength/(focalLength + z3); 
            t4 = focalLength/(focalLength + z4); 
             
            // determine triangle vertices based on t values 
            var vertices:Vector.<Number> = new Vector.<Number>(); 
            vertices.push(x1*t1,y1*t1, x2*t2,y2*t2, x3*t3,y3*t3, x4*t4,y4*t4); 
            // set T values allowing perspective to change 
            // as each vertex moves around in z space 
            var uvtData:Vector.<Number> = new Vector.<Number>(); 
            uvtData.push(0,0,t1, 1,0,t2, 1,1,t3, 0,1,t4); 
             
            // draw 
            container.graphics.clear(); 
            container.graphics.beginBitmapFill(bitmapData); 
            container.graphics.drawTriangles(vertices, indices, uvtData); 
        } 
    } 
}

Per provare questo esempio, salvate questi due file di classe nella stessa directory come immagine con nome “ocean.jpg”. Noterete come la bitmap originale viene trasformata in modo che sembri che svanisca in lontananza e ruoti nello spazio tridimensionale.

Culling

Il termine culling indica il processo che determina quali superfici di un oggetto 3D non devono essere elaborate dal renderer poiché non sono visibili dal punto di vista corrente. Nello spazio tridimensionale, la superficie del “retro” di un oggetto 3D è nascosto rispetto al punto di vista:
Il retro di un oggetto 3D non è visibile dal punto di vista
A.
Punto di vista

B.
Oggetto 3D

C.
Retro dell'oggetto 3D

Il rendering dei triangoli viene effettuato sempre, a prescindere dalla loro dimensione, dalla loro forma o dalla loro posizione. Il culling garantisce che Flash Player o AIR effettuino correttamente il rendering di un oggetto 3D. Inoltre, per limitare il numero di cicli di rendering, talvolta potete fare in modo che per alcuni triangoli non venga effettuato il rendering. Prendete in considerazione un cubo che ruota nello spazio. In qualsiasi momento, non vedete più di tre lati del cubo poiché i lati non visibili sono rivolti nella direzione opposta. Poiché questi lati non vengono comunque visualizzati, il renderer non li disegna. Senza il culling, Flash Player o AIR effettuano il rendering sia del lato anteriore che di quello posteriore.

Un cubo ha dei lati non visibili dal punto di vista corrente

Pertanto, il metodo Graphics.drawTriangles() è dotato di un quarto parametro per stabilire un valore di culling:

public function drawTriangles(vertices:Vector.<Number>, indices:Vector.<int> = null, uvtData:Vector.<Number> = null, culling:String = "none"):void

Il parametro di culling è un valore della classe di enumerazione TriangleCulling: TriangleCulling.NONE, TriangleCulling.POSITIVE e TriangleCulling.NEGATIVE. Questi valori sono dipendenti dalla direzione del tracciato di triangoli che definisce la superficie dell'oggetto. L'API ActionScript per determinare il culling presume che tutti i triangoli rivolti verso l'esterno di una forma 3D vengano disegnati con la stessa direzione di tracciato. Quando il lato del triangolo viene girato, cambia anche la direzione del tracciato. A quel punto, potete applicare il culling al triangolo (che viene escluso dal rendering).

Quindi, un valore TriangleCullingPOSITIVE rimuove i triangoli con una direzione di tracciato positiva (senso orario). Un valore TriangleCullingNEGATIVE rimuove i triangoli con una direzione di tracciato negativa (senso antiorario). Nel caso di un cubo, mentre le superfici rivolte verso il punto di vista hanno una direzione di tracciato positiva, quelle rivolte verso il retro hanno una direzione di tracciato negativa:

Un cubo “aperto” per mostrare la direzione del tracciato: una volta “chiuso”, la direzione del tracciato del lato posteriore risulta invertita.

Per scoprire come funziona il culling, iniziate con il primo esempio di Mappatura UV e impostate il parametro di culling del metodo drawTriangles() su TriangleCulling.NEGATIVE:

container.graphics.drawTriangles(vertices, indices, uvtData, TriangleCulling.NEGATIVE);

Noterete che non viene effettuato il rendering del lato “posteriore” dell'immagine mentre l'oggetto ruota.