Använda trianglar för 3D-effekter

Flash Player 10 och senare, Adobe AIR 1.5 och senare

I ActionScript utför du bitmappsomformningar med metoden Graphics.drawTriangles() , eftersom 3D-modeller representeras av en mängd trianglar i rymden. (Flash Player och AIR har emellertid inget stöd för djupbuffring, så visningsobjekten är fortfarande platta, eller tvådimensionella. Detta beskrivs i Om 3D-visningsobjekt i Flash Player och i AIR-miljön .) Metoden Graphics.drawTriangles() påminner om metoden Graphics.drawPath() på det sätt att den använder en uppsättning koordinater för att rita en triangelbana.

Mer information om hur du använder Graphics.drawPath() finns i Rita banor .

Metoden Graphics.drawTriangles() använder en Vector.<Number>-objekt för att ange triangelbanans punktplaceringar:

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

Den första parametern i drawTriangles() är den enda obligatoriska parametern: vertices . Den här parametern är en vektor med siffror som definierar koordinaterna som trianglarna ritas genom. Alla tre uppsättningar koordinater (sex siffror) representerar en triangelbana. Utan parametern indices bör längden på vektorn alltid ha faktorn sex, eftersom varje triangel måste ha tre koordinatpar (tre uppsättningar med två x/y-värden). Till exempel:

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

Ingen av dessa trianglar har gemensamma punkter, men om så vore fallet skulle den andra drawTriangles() -parametern, indices , kunna användas för att återanvända värdena i vektorn vertices för mer än en triangel.

När du använder parametern indices bör du vara medveten om att indices -värdena är punktindex, inte index som relaterar direkt till vertices -arrayelementen. Med andra ord, ett index i vektorn vertices som definieras med indices är i själva verket det verkliga indexet dividerat med 2. För den tredje punkten i en vertices -vektor kan du till exempel använda indices -värdet 2, även om det första numeriska värdet för den punkten startar vid vektorindex 4.

Sammanfoga till exempel två trianglar så att de delar den diagonala kanten med parametern indices :

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

Observera, att även om fyrkanten nu har ritats med hjälp av två trianglar, har bara fyra punkter angetts i vertices -vektorn. Om du använder indices kommer de två punkterna som delas av de två trianglarna att återanvändas för varje triangel. Då minskar det totala antalet hörn från 6 (12 siffror) till 4 (8 siffror):

En fyrkant som ritas med två trianglar med hjälp av parametern vertices

Den här tekniken kan vara användbar vid större triangelnät där de flesta punkter är gemensamma för flera trianglar.

Alla fyllningar kan tillämpas på trianglar. Fyllningarna används på det färdiga triangelnätet på samma sätt som på andra former.

Omforma bitmappar

Bitmappsomformningar skapar en illusion av perspektiv eller ”textur” på ett tredimensionellt objekt. Du kan förvränga en bitmapp mot en flyktpunkt, så att bilden verkar krympa allt eftersom den flyttar mot flyktpunkten. Alternativt kan du använda en tvådimensionell bitmapp om du vill skapa en yta för ett tredimensionellt objekt, och skapa en illusion av textur eller ”ihopvikning” av det tredimensionella objektet.

En tvådimensionell yta som använder en flyktpunkt och ett tredimensionellt objekt som har vikts ihop med en bitmapp.

UV-mappning

När du har börjat arbeta med texturer vill du förmodligen också använda uvtData-parametern för drawTriangles() . Med den här parametern kan du ställa in UV-mappning för bitmappsfyllningar.

UV-mappning är en metod för att texturera objekt. Den utnyttjar två värden, ett U-vågrätt värde (x) och ett V-lodrätt värde (y). I stället för att baseras på pixelvärden, baseras de på procent. 0 U och 0 V innebär det övre vänstra hörnet i en bild och 1 U och 1 V det nedre högra hörnet:

Platserna UV 0 och 1 i en bitmappsbild

Vektorerna för en triangel kan tilldelas UV-koordinater om du vill koppla dem till respektive plats i en bild:

UV-koordinaterna i ett triangulärt område i en bitmappsbild

UV-värdena fortsätter att motsvara punkterna i triangeln:

Hörnen i triangeln flyttar och bitmappen förvrängs så att UV-värdena för de enskilda punkterna inte ändras

När ActionScript 3D-omformningarna används på triangeln som hör till bitmappen, används bitmappsbilden på triangeln utifrån UV-värdena. I stället för att använda matrisberäkningar, ska du ställa in eller justera UV-värdena om du vill skapa en tredimensionell effekt.

Metoden Graphics.drawTriangles() kan även innehålla ytterligare information för tredimensionella omformningar: T-värdet. T-värdet i uvtData representerar 3D-perspektivet, eller mer specifikt, skalfaktorn för det tillhörande hörnet. UVT-mappning tillför perspektivkorrigering i UV-mappningen. Exempel: om ett objekt placeras i 3D-ytan bort från vypunkten så att det verkar vara 50 % av den ursprungliga storleken, blir T-värdet för det objektet 0,5. Eftersom trianglarna ritas för att representera objekt i 3D-rymden, bestämmer deras placering utmed z-axeln deras T-värden. Följande ekvation bestämmer T-värdet:

T = focalLength/(focalLength + z);
I den här ekvationen representerar focalLength en brännvidd eller beräknad ”skärmplacering” som bestämmer hur mycket perspektiv som finns i bilden.
Brännvidd och z-värde
A.
vypunkt

B.
skärm

C.
3D-objekt

D.
värdet på focalLength

E.
z-värde

Värdet på T används för att skala enkla former så att de verkar vara längre bort. Det är vanligtvis detta värde som används för att konvertera 3D-punkter till 2D-punkter. När det gäller UVT-data används det också för att skala en bitmapp mellan punkterna i en triangel med perspektiv.

När du definierar UVT-värden följer T-värdet de UV-värden som har definierats för ett hörn. Med detta ”T” matchas vart tredje värde i uvtData -parametern (U, V och T) mot vartannat värde i vertices -parametern (x och y). Med enbart UV-värden blir uvtData.length == vertices.length. Om du tar med ett T-värde blir uvtData.length = 1.5*vertices.length.

I följande exempel visas ett plan som roteras i 3D-rymden med hjälp av UVT-data. I det här exemplet används en bild med namnet ocean.jpg och en hjälpklass, ImageLoader, för att läsa in bilden ocean.jpg så att den kan tilldelas BitmapData-objektet.

Här är ImageLoader-klasskällan (spara den här koden i en fil med namnet 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); 
    } 
    } 
}

Och här är det ActionScript som använder trianglar, UV-mappning och T-värden för att göra så att bilden ser ut som om den krymper mot flyktpunkten och roterar. Spara den här koden i en fil med namnet 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); 
        } 
    } 
}

Om du vill testa det här exemplet sparar du dessa två klassfiler i samma katalog som en bild med namnet ocean.jpg. Du ser hur den ursprungliga bitmappen omformas så att det verkar som om den försvinner med avståndet och roterar i 3D-rymden.

Culling

Culling är den process som bestämmer vilka ytor i ett tredimensionellt objekt som inte ska återges, eftersom de döljs från den aktuella vypunkten. I 3D-rymden är ytan på baksidan av ett tredimensionellt objekt dolt från vypunkten:
Baksidan på ett 3D-objekt är dolt från vypunkten.
A.
vypunkt

B.
3D-objekt

C.
baksidan på ett tredimensionellt objekt

Alla trianglar återges alltid oavsett storlek, form eller position. Culling säkerställer att Flash Player eller AIR återger 3D-objekten på rätt sätt. Om du vill ha färre återgivningscykler kanske du vill att vissa trianglar hoppas över vid återgivningen. Tänk på en kub som roterar i rymden. Vid en given tidpunkt ser du aldrig mer än tre sidor av den kuben, eftersom de sidor som inte visas riktas åt ett annat håll på den andra sidan av kuben. Eftersom dessa sidor inte visas, bör de inte ritas vid återgivningen. Utan culling återges både fram- och baksidan i Flash Player och AIR.

En kub har sidor som inte visas från den aktuella vypunkten

Därför har metoden Graphics.drawTriangles() en fjärde parameter som upprättar ett culling-värde:

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

Culling-parametern är ett värde från TriangleCulling -uppräkningsklassen: TriangleCulling.NONE , TriangleCulling.POSITIVE och TriangleCulling.NEGATIVE . Dessa värden är beroende av triangelbanans riktning som definierar objektets yta. ActionScript-API:et för att bestämma culling förutsätter att alla utåtriktade trianglar i en 3D-form ritas med samma banriktning. När en triangelyta vänds ändras också banans riktning. Vid den punkten kan culling användas på triangeln (det vill säga ingen återgivning sker).

TriangleCulling -värdet POSITIVE tar bort trianglar med positiv banriktning (medurs). TriangleCulling -värdet NEGATIVE tar bort trianglar med negativ banriktning (moturs). När det gäller en kub, har dess sidor som riktas framåt en positiv banriktning medan sidorna som riktas bakåt har en negativ banriktning:

En utvecklad kub där banriktningen visas. Ihopvikt är baksidans banriktning den omvända.

Om du vill se hur culling fungerar börjar du med ett tidigare exempel från UV-mappning och ställer in culling-parametern för metoden drawTriangles() TriangleCulling.NEGATIVE :

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

Observera att baksidan på bilden inte återges när objektet roteras.