Uso de triángulos para efectos 3D

Flash Player 10 y posterior, Adobe AIR 1.5 y posterior

En ActionScript, las transformaciones de mapa de bits se realizan utilizando el método Graphics.drawTriangles() , ya que los modelos 3D se representan mediante una colección de triángulos en el espacio. (No obstante, Flash Player y AIR no admiten un búfer de profundidad, por lo que los objetos de visualización aún son esencialmente planos o bidimensionales. Esto se describe en Información sobre los objetos de visualización 3D en el motor de ejecución de Flash Player y AIR .) El método Graphics.drawTriangles() es similar a Graphics.drawPath() , ya que adopta un conjunto de coordenadas para dibujar un trazado de triángulo.

Para familiarizarse con el uso de Graphics.drawPath() , consulte Trazados de dibujo .

El método Graphics.drawTriangles() utiliza Vector.<Number> para especificar las ubicaciones de punto para el trazado del triángulo:

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

El primer parámetro de drawTriangles() es el único necesario: el parámetro vertices . Este parámetro es un vector de números que define las coordenadas mediante las que se dibujan los triángulos. Cada tres conjuntos de coordenadas (seis números) se representa un trazado del triángulo. Sin el parámetro indices , la longitud del vector siempre debe ser un factor de seis, ya que cada triángulo requiere tres pares de coordenadas(tres conjuntos de dos valores x/y). Por ejemplo:

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

Ninguno de estos triángulos comparte puntos, pero si lo hicieran, el segundo parámetro drawTriangles() , indices , podría utilizarse para volver a usar los valores en el vector vertices para varios triángulos.

Al utilizar el parámetro indices , se debe tener en cuenta que los valores indices son índices de punto, y no índices que se relacionan directamente con los elementos del conjunto vertices . Es decir, un índice del vector vertices tal y como se define mediante indices es realmente el verdadero índice dividido por 2. Para el tercer punto de un vector vertices utilice, por ejemplo, un valor indices de 2, aunque el primer valor numérico de ese punto comience en el índice vectorial de 4.

Por ejemplo, combine dos triángulos para que compartan el borde diagonal utilizando el 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]));

Se debe tener en cuenta que aunque ahora se haya dibujado un cuadrado utilizando dos triángulos, solo se especificaron cuatro puntos en el vector vertices . Con el uso de indices , los dos puntos compartidos por los dos triángulos se volverán a utilizar para cada triángulo. Con ello se reduce el recuento total de vértices de 6 (12 números) a 4 (8 números):

Cuadrado dibujado con dos triángulos utilizando el parámetro de vértices

Esta técnica resulta útil con mallas de triángulo más grandes donde varios triángulos comparten la mayoría de los puntos.

Todos los rellenos se pueden aplicar a triángulos. Los rellenos se aplican a la malla de triángulo resultante tal y como se haría en cualquier otra forma.

Transformación de mapas de bits

Las transformaciones de mapa de bits proporcionan la ilusión de perspectiva o "textura" en un objeto tridimensional. Concretamente, es posible distorsionar un mapa de bits hacia un punto de fuga de forma que la imagen parezca encogerse conforme se acerca al punto de fuga. O bien, puede utilizar un mapa de bits bidimensional para crear una superficie para un objeto tridimensional, ofreciendo la ilusión de textura o “envoltura” en dicho objeto.

Superficie bidimensional donde se utiliza un punto de fuga y tres objetos tridimensionales ajustados con un mapa de bits

Mapeado UV

Una vez que comience a trabajar con texturas, deseará utilizar el parámetro uvtData de drawTriangles() . Este parámetro permite configurar el mapeado UV para rellenos de mapa de bits.

El mapeado UV es un método para aplicar textura a los objetos. Se basa en dos valores: un valor U horizontal (x) y un valor V vertical (y). En lugar de basarse en valores de píxel, se basan en porcentajes. 0 U y 0 V representan la parte superior izquierda de una imagen y 1 U y 1 V la parte inferior derecha:

Ubicaciones UV 0 y 1 en una imagen de mapa de bits

A los vectores de un triángulo se les puede proporcionar coordenadas UV para asociarse con las ubicaciones respectivas en una imagen:

Coordenadas UV de un área triangular de una imagen de mapa de bits

Los valores UV coinciden con los puntos del triángulo:

Los vértices del triángulo se mueven y el mapa de bits se distorsiona para mantener igual los valores UV para un punto individual

Debido a que las transformaciones 3D de ActionScript se aplican al triángulo asociado con el mapa de bits, la imagen del mapa de bits se aplica al triángulo en función de los valores UV. Por lo tanto, en lugar de utilizar cálculos de matriz, establezca o ajuste los valores UV para crear un efecto tridimensional.

El método Graphics.drawTriangles() también acepta una parte de información opcional para transformaciones tridimensionales: el valor T. El valor T en uvtData representa la perspectiva 3D, o más concretamente, el factor de escala del vértice asociado. El mapeado UVT añade una corrección de la perspectiva al mapeado UV. Por ejemplo, si un objeto se coloca en un espacio 3D alejado del punto de vista de forma que parezca ser un 50% de su tamaño “original”, el valor T del objeto podría ser 0,5. Debido a que los triángulos se dibujan para representar objetos en el espacio 3D, sus ubicaciones en el eje z determinan sus valores T. La ecuación que determina el valor T es la siguiente:

T = focalLength/(focalLength + z);
En esta ecuación, focalLength representa una distancia focal o ubicación de "pantalla" calculada que determina la cantidad de perspectiva que se ofrece en la vista.
Distancia focal y valor z
A.
Punto de vista

B.
pantalla

C.
Objeto 3D

D.
valor focalLength

E.
valor z

El valor de T se utiliza para realizar un ajuste de escala de formas básicas para hacerlas parecer más alejadas en la distancia. Suele ser el valor utilizado para convertir puntos 3D en puntos 2D. En el caso de datos UVT, también se utiliza para escalar un mapa de bits entre los puntos en un triángulo con perspectiva.

Cuando se definen valores UVT, el valor T sigue directamente los valores UV definidos para un vértice. Con la inclusión de T, cada tres valores en el parámetro uvtData (U, V y T) coinciden con cada dos valores del parámetro vertices (x e y). Solo con los valores UV, uvtData.length == vertices.length. Con la inclusión de un valor T, uvtData.length = 1.5*vertices.length.

En el siguiente ejemplo se muestra un plano giratorio en un espacio 3D utilizando datos UVT. Este ejemplo utiliza una imagen llamada ocean.jpg y una clase “auxiliar”, ImageLoader, para cargar la imagen ocean.jpg de modo que se pueda asignar al objeto BitmapData.

A continuación se muestra el código fuente de la clase ImageLoader (guarde este código en un archivo denominado 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); 
    } 
    } 
}

Y a continuación se muestra el código ActionScript que utiliza triángulos, mapeado UV y valores T para hacer que la imagen parezca que está rotando y encogiéndose hacia un punto de fuga. Guarde este código en un archivo 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 probar este ejemplo, guarde los dos archivos de clase en el mismo directorio que una imagen llamada “ocean.jpg”. Puede ver cómo se transforma el mapa de bits original para que parezca que se aleja en la distancia y gira en un espacio 3D.

Técnica "culling"

La técnica "culling" es el proceso que determina qué superficies de un objeto tridimensional no debe representar el procesador porque están ocultas desde el punto de vista actual. En el espacio 3D, la superficie de la parte “posterior” de un objeto tridimensional queda oculta desde el punto de vista:
La parte posterior de un objeto 3D se oculta desde el punto de vista.
A.
Punto de vista

B.
Objeto 3D

C.
Parte posterior de un objeto tridimensional

Los triángulos siempre se representan independientemente de su tamaño, forma o posición. La técnica "culling" garantiza que Flash Player o AIR representen los objetos 3D correctamente. Asimismo, para guardar los ciclos de representación, a veces se desea que el procesador omita algunos triángulos. Pongamos como ejemplo un cubo giratorio en el espacio. En un momento dado, nunca verá más de tres caras del cubo, ya que las caras ocultas estarán frente a la otra dirección del otro lado del cubo. Debido a que estas caras no se van a ver, el procesador no debe dibujarlas. Sin "culling", Flash o AIR representan tanto las caras frontales como las posteriores.

Un cubo tiene caras no visibles desde el punto de vista actual

Por lo tanto, el método Graphics.drawTriangles() tiene un cuarto parámetro para establecer un valor de "culling":

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

El parámetro de "culling" es un valor de la clase de enumeración TriangleCulling : TriangleCulling.NONE , TriangleCulling.POSITIVE y TriangleCulling.NEGATIVE . Estos valores son dependientes de la dirección del trazado del triángulo que define la superficie del objeto. La API de ActionScript para determinar el valor "culling" da por hecho que todos los triángulos exteriores de una forma 3D se dibujan con la misma dirección de trazado. Cuando una cara del triángulo cambia de dirección, la dirección del trazado también se modifica. En este momento, al triángulo se le puede aplicar el proceso de "culling" (eliminarse de la representación).

Por lo tanto, un valor TriangleCulling de POSITIVE elimina triángulos con una dirección de trazado positiva (en el sentido de las agujas del reloj). Un valor TriangleCulling de NEGATIVE elimina triángulos con una dirección de trazado negativa (en sentido contrario a las agujas del reloj). En el caso de un cubo, mientras que las caras frontales tienen una dirección de trazado positiva, las posteriores cuentan con una dirección de trazado negativa:

Cubo “sin ajustar” para mostrar la dirección de trazado. Cuando se “ajusta”, se invierte la dirección de trazado de la cara posterior

Para ver cómo funciona la técnica "culling", comience con el ejemplo anterior de Mapeado UV , establezca el parámetro de "culling" del método drawTriangles() en TriangleCulling.NEGATIVE :

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

Observe que la parte “posterior” de la imagen no se procesa conforme gira el objeto.