Uso de triângulos para obter efeitos 3D

Flash Player 10 e posterior, Adobe AIR 1.5 e posterior

No ActionScript, você executa transformações de bitmap usando o método Graphics.drawTriangles() , pois os modelos 3D são representados por um conjunto de triângulos no espaço. (Contudo, o Flash Player e o AIR não dão suporte a um buffer de profundidade, por isso os objetos de exibição são inerentemente planos, ou 2D. Isso é descrito em Noções básicas de objetos de exibição 3D do Flash Player e de tempo de execução do AIR .) O método Graphics.drawTriangles() é parecido com o método Graphics.drawPath() no sentido de que usa um conjunto de coordenadas para desenhar um caminho de triângulo.

Para se familiarizar com o uso de Graphics.drawPath() , consulte Caminhos de desenho .

O método Graphics.drawTriangles() usa um Vector.<Number> para especificar as localizações das pontas para o caminho do triângulo:

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

O primeiro parâmetro de drawTriangles() é o único necessário: o parâmetro vertices . Este parâmetro é um vetor de números que define as coordenadas através das quais os seus triângulos são desenhados. Cada três conjuntos de coordenadas (seis números) representa um caminho de triângulo. Sem o parâmetro indices , o comprimento do vetor deve sempre ser um fator de seis, uma vez que cada triângulo requer três pares de coordenadas (três conjuntos de dois valores x/y). Por exemplo:

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

Nenhum desses triângulos compartilham pontas, mas, se compartilhassem, o segundo parâmetro de drawTriangles() , indices , poderia ser usado para reutilizar valores do vetor vertices para mais de um triângulo.

Quando usar o parâmetro indices , é importante que você saiba que os valores de indices são índices de pontos e não índices relacionados diretamente aos elementos de matriz vertices . Em outras palavras, um índice no vetor vertices conforme definido por indices é, na verdade, o índice real dividido por 2. Para a terceira ponta de um vetor vertices , por exemplo, use um valor de 2 para indices , mesmo que o primeiro valor numérico dessa ponta comece no índice de vetor de 4.

Por exemplo, mescle dois triângulos para que compartilhem a aresta diagonal usando o parâmetro indices :

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

Observe que, apesar de agora ter sido desenhado um quadrado usando-se dois triângulos, somente quatro pontas foram especificadas no vetor vertices . Usando indices , as duas pontas compartilhadas pelos dois triângulos são reutilizadas para cada triângulo. Isso diminui a contagem geral de vértices de 6 (12 números) para 4 (8 números):

Um quadrado desenhado com dois triângulos usando o parâmetro vertices

Esta técnica torna-se útil com malhas de triângulos maiores, onde a maioria das pontas são compartilhadas por vários triângulos.

Todos os preenchimentos podem ser aplicados a triângulos. Os preenchimentos são aplicados à malha de triângulos resultante, assim como ocorre com qualquer outra forma.

Transformação de bitmaps

As transformações de bitmap dão a ilusão de perspectiva ou "textura" em um objeto tridimensional. Especificamente, você pode distorcer um bitmap em direção a um ponto de fuga de modo que a imagem pareça diminuir à medida que se aproxima do ponto de fuga. Se preferir, você pode usar um bitmap bidimensional para criar uma superfície para um objeto tridimensional, dando a ilusão de textura ou “wrapping” nesse objeto 3D.

Uma superfície bidimensional usando um ponto de fuga e um objeto tridimensional delimitado com um bitmap.

Mapeamento UV

Depois que começar a trabalhar com texturas, você desejará usar o parâmetro uvtData de drawTriangles() . Esse parâmetro permite configurar o mapeamento UV para preenchimentos de bitmap.

O mapeamento UV é um método de aplicar textura a objetos. Ele se baseia em dois valores: um valor horizontal U (x) e um valor vertical V (y). Em vez de serem baseados em valores de pixel, eles são baseados em porcentagens. 0 U e 0 V é o canto superior esquerdo de uma imagem, e 1 U e 1 V é o canto inferior direito:

As localizações UV 0 e 1 em uma imagem de bitmap

Os vetores de um triângulo podem receber coordenadas UV para se associarem às respectivas localizações em uma imagem:

As coordenadas UV de uma área triangular de uma imagem de bitmap

Os valores UV permanecem consistentes com as pontas do triângulo:

Os vértices do triângulo são movimentados e o bitmap é distorcido para manter iguais os valores UV de uma ponta individual

Conforme transformações 3D do ActionScript são aplicadas ao triângulo associado ao bitmap, a imagem do bitmap é aplicada ao triângulo com base nos valores UV. Portanto, em vez de usar cálculos de matriz, defina ou ajuste os valores UV para criar um efeito tridimensional.

O método Graphics.drawTriangles() também aceita uma informação opcional para transformações tridimensionais: o valor T. O valor T em uvtData representa a perspectiva 3D ou, mais especificamente, o fator de escala do vértice associado. O mapeamento UVT adiciona correção de perspectiva ao mapeamento UV. Por exemplo, se um objeto estiver posicionado no espaço 3D longe do ponto de visão, de modo que pareça ter 50% de seu tamanho “original”, o valor T desse objeto será 0,5. Uma vez que triângulos são desenhados para representar objetos no espaço 3D, sua localização no eixo z determina seus valores T. A equação que determina o valor T é a seguinte:

T = focalLength/(focalLength + z);
Nesta equação, focalLength representa uma distância focal ou uma localização "na tela" calculada, que determina o grau da perspectiva observado na exibição.
A distância focal e o valor z
A.
ponto de visão

B.
tela

C.
objeto 3D

D.
valor de focalLength

E.
valor z

O valor de T é usado para dimensionar formas básicas para fazer com que pareçam mais distantes. Normalmente é o valor usado para converter pontos 3D em pontos 2D. No caso de dados UVT, também é usado para dimensionar um bitmap entre as pontas de um triângulo com perspectiva.

Quando você define valores UVT, o valor T segue diretamente os valores UV definidos para um vértice. Com a inclusão de T, cada três valores no parâmetro uvtData (U, V e T) correspondem a cada dois valores no parâmetro vertices (x e y). Com valores UV apenas, uvtData.length == vertices.length. Com a inclusão de um valor T, uvtData.length = 1,5*vertices.length.

O exemplo a seguir mostra um plano sendo girado no espaço 3D usando dados UVT. Este exemplo usa uma imagem chamada ocean.jpg e uma classe “auxiliar” (ImageLoader) para carregar a imagem de maneira que ela possa ser atribuída ao objeto BitmapData.

Este é o código-fonte da classe ImageLoader (salve-o em um arquivo chamado 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); 
    } 
    } 
}

E este é o ActionScript que usa triângulos, mapeamento UV e valores T para fazer com que a imagem pareça estar diminuindo à medida que se aproxima de um ponto de fuga e girando. Salve este código em um arquivo denominado 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); 
        } 
    } 
}

Para testar este exemplo, salve esses dois arquivos de classe no mesmo diretório de uma imagem chamada “ocean.jpg”. Você pode ver como o bitmap original é transformado para dar a impressão de que ele está desaparecendo conforme aumenta a distância e girando no espaço 3D.

Remoção

Remoção é o processo que determina quais superfícies de um objeto tridimensional o renderizador não deve renderizar pelo fato de estarem ocultas do ponto de visão atual. No espaço 3D, a superfície na “parte de trás” de um objeto tridimensional fica oculta do ponto de visão:
A parte de trás de um objeto 3D fica oculta do ponto de visão.
A.
ponto de visão

B.
objeto 3D

C.
a parte de trás de um objeto tridimensional

Inerentemente, todos os triângulos sempre são renderizados, seja qual for o tamanho, a forma ou a posição. A remoção assegura que o Flash Player ou o AIR renderize o objeto 3D corretamente. Além disso, para ganhar tempo nos ciclos de renderização, às vezes você deseja que alguns triângulos sejam ignorados pelo renderizador. Pense em um cubo girando no espaço. Em um determinado momento, você não verá mais do que três lados desse cubo, uma vez que os lados que não aparecem na exibição estariam voltados para a outra direção no outro lado do cubo. Como esses lados não serão vistos, o renderizados não deve desenhá-los. Sem a remoção, o Flash Player ou o AIR renderiza os lados da frente e de trás.

Um cubo tem lados que não ficam visíveis no ponto de visão atual

Por isso, o método Graphics.drawTriangles() tem um quarto parâmetro para estabelecer um valor de remoção:

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

O parâmetro de remoção é um valor da classe de enumeração TriangleCulling : TriangleCulling.NONE , TriangleCulling.POSITIVE e TriangleCulling.NEGATIVE . Esses valores dependem da direção do caminho do triângulo que define a superfície do objeto. A API do ActionScript para determinar a remoção pressupõe que todos os triângulos voltados para fora de uma forma 3D sejam desenhados com a mesma direção de caminho. Uma vez que uma face do triângulo é virada, a direção do caminho também muda. Nesse ponto, o triângulo pode ser removido (excluído da renderização).

Portanto, um valor POSITIVE de TriangleCulling remove triângulos com direção de caminho positiva (no sentido horário). Um valor NEGATIVE de TriangleCulling remove triângulos com uma direção de caminho negativa (no sentido anti-horário). No caso de um cubo, enquanto as superfícies voltadas para frente têm uma direção de caminho positiva, as superfícies traseiras têm uma direção de caminho negativa:

Um cubo “aberto” para mostrar a direção do caminho. Quando o cubo está “fechado”, a direção do caminho da parte traseira é invertida.

Para ver como funciona a remoção, comece com o exemplo anterior de Mapeamento UV , defina o parâmetro de remoção do método drawTriangles() como TriangleCulling.NEGATIVE :

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

Observe que o lado “de trás” da imagem não é renderizado porque o objeto gira.