Création d’effets 3D à l’aide de triangles

Flash Player 10 et les versions ultérieures, Adobe AIR 1.5 et les versions ultérieures

Dans ActionScript, vous effectuez des transformations de bitmap à l’aide de la méthode Graphics.drawTriangles() , car les modèles 3D sont représentés par un ensemble de triangles dans l’espace. (Flash Player et AIR ne prennent néanmoins pas en charge une mémoire tampon de profondeur, car les objets d’affichage sont toujours essentiellement plats, ou 2D. Pour plus d'informations, voir Présentation des objets d’affichage 3D dans les moteurs d’exécution de Flash Player et d’AIR .) La méthode Graphics.drawTriangles() est similaire à la méthode Graphics.drawPath() , en ce qu’elle accepte un ensemble de coordonnées pour tracer un tracé triangulaire.

Pour vous familiariser avec l’utilisation de Graphics.drawPath() , voir Tracés de dessin .

La méthode Graphics.drawTriangles() utilise une propriété Vector.<Number> pour spécifier l’emplacement des points sur le tracé triangulaire :

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

Le premier paramètre de drawTriangles() , vertices , est le seul paramètre obligatoire. C’est un vecteur de nombres définissant les coordonnées par lesquelles vos triangles sont tracés. Trois ensembles de coordonnées (six nombres) représentent un tracé triangulaire. Sans le paramètre indices , la longueur du vecteur doit systématiquement être un facteur de six, car chaque triangle nécessite trois paires de coordonnées (trois ensembles de deux valeurs x/y). Exemple :

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

Ces triangles n’ont pas de points en commun, mais si tel était le cas, le second paramètre de drawTriangles() , indices , permettrait de réutiliser des valeurs du vecteur vertices pour plusieurs triangles.

Lorsque vous utilisez le paramètre indices , gardez à l’esprit le fait que les valeurs indices représentent des index de point, pas des index en rapport direct avec les éléments du tableau vertices . En d’autres termes, un index du vecteur vertices tel qu’il est défini par indices correspond en fait à l’index réel divisé par 2. Pour le troisième point d’un vecteur vertices , par exemple, utilisez une valeur indices de 2, même si la première valeur numérique de ce point commence à l’index vectoriel 4.

Par exemple, fusionnez deux triangles de sorte qu’ils aient en commun le bord diagonal, par le biais du paramètre indices :

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

Vous remarquerez que, bien qu’un carré résulte du tracé de deux triangles, seuls quatre points ont été spécifiés dans le vecteur vertices . Grâce à indices , les deux points partagés par les deux triangles sont réutilisés pour chacun d’eux. Le nombre total de sommets passe donc de 6 (12 nombres) à 4 (8 nombres).

Carré tracé à l’aide de deux triangles à l’aide du paramètre vertices

Cette technique s’avère utile pour les maillages triangulaires plus grands, dans lesquels la plupart des points sont partagés par plusieurs triangles.

Il est possible d’appliquer tous les remplissages aux triangles. Ils sont appliqués au maillage triangulaire résultant comme ils le seraient à toute autre forme.

Transformation d’images bitmap

Les transformations de bitmap donnent une illusion de perspective ou « texture » à un objet en trois dimensions. Vous pouvez notamment distordre une bitmap en direction d’un point de fuite, afin que l’image semble diminuer à mesure qu’elle se rapproche de celui-ci. Vous pouvez aussi utiliser une bitmap en deux dimensions pour créer une surface sur un objet en trois dimensions, donnant ainsi l’impression qu’il possède une texture ou « enveloppe ».

Surface en deux dimensions utilisant un point de fuite et objet en trois dimensions enveloppé dans une bitmap

Mappage des coordonnées UV

Lorsque vous commencerez à manipuler les textures, vous souhaiterez utiliser le paramètre uvtData de drawTriangles() . Ce paramètre vous permet de définir le mappage des coordonnées UV pour les remplissages de bitmap.

Le mappage des coordonnées UV est une méthode d’application d’une texture à des objets. Il repose sur deux valeurs, une valeur U horizontale (x) et une valeur V verticale (y). Ces valeurs sont basées sur des pourcentages et non sur des valeurs de pixels. 0 U et 0 V correspondent au coin supérieur gauche d’une image, 1 U et 1 V à son coin inférieur droit :

Emplacements UV 0 et 1 sur une image bitmap

Il est possible d’affecter des coordonnées UV aux vecteurs d’un triangle de sorte qu’ils s’associent aux emplacements respectifs sur une image :

Coordonnées UV d’une zone triangulaire sur une image bitmap

Les valeurs UV restent en phase avec les points du triangle.

Les sommets du triangle se déplacent et l’image bitmap se distord pour que les valeurs UV d’un point individuel restent identiques.

Au fur et à mesure que des transformations 3D ActionScript sont appliquées au triangle associé à la bitmap, celle-ci est appliquée au triangle en fonction des valeurs UV. Par conséquent, au lieu d’utiliser des calculs matriciels, définissez ou réglez les valeurs UV pour créer un effet tridimensionnel.

La méthode Graphics.drawTriangles() accepte également une information facultative pour les transformations tridimensionnelles : la valeur T. La valeur T de uvtData représente la perspective 3D ou, plus spécifiquement, le facteur d’échelle du sommet associé. Le mappage des coordonnées UVT ajoute une correction de perspective au mappage des coordonnées UV. Par exemple, si un objet de l’espace 3D est éloigné du point de vue de telle sorte qu’il semble mesurer 50 % de sa taille « d’origine », sa valeur T correspond à 0,5. Comme les objets de l’espace 3D sont représentés à l’aide de triangles, l’emplacement de ceux-ci le long de l’axe z détermine leur valeur T. L’équation qui représente la valeur T est la suivante :

T = focalLength/(focalLength + z);
Dans cette équation, focalLength représente une distance focale ou un emplacement à l’« écran » calculé qui détermine la quantité de perspective de l’affichage.
Distance focale et valeur z
A.
point de vue

B.
écran

C.
objet 3D

D.
valeur focalLength

E.
valeur z

La valeur T permet de mettre à l’échelle des formes simples pour donner l’impression qu’elles se trouvent au loin. C’est généralement la valeur utilisée pour convertir les points 3D en points 2D. Dans le cas des données UVT, elle permet aussi de mettre à l’échelle une bitmap entre les points d’un triangle avec perspective.

Lorsque vous définissez des valeurs UVT, la valeur T suit directement les valeurs UV définies pour un sommet. Avec l’inclusion de T, chaque trio de valeurs du paramètre uvtData (U, V et T) correspond à chaque paire de valeurs du paramètre vertices (x et y). Avec les valeurs UV seules, uvtData.length == vertices.length. Avec l’inclusion d’une valeur T, uvtData.length = 1,5*vertices.length.

L’exemple suivant illustre un plan qui pivote, par le biais de données UVT, dans un espace 3D. Il utilise l’image ocean.jpg et une classe « d’interaction », ImageLoader, qui charge l’image afin qu’il soit possible de l’affecter à l’objet BitmapData.

La source de la classe ImageLoader (enregistrez ce code dans le fichier ImageLoader.as) se présente comme suit :

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); 
    } 
    } 
}

Le code ActionScript qui utilise des triangles, le mappage des coordonnées UV et des valeurs T pour que l’image semble pivoter et diminuer au fur et à mesure qu’elle se rapproche d’un point de fuite est indiqué ci-dessous. Enregistrez-le dans un fichier que vous nommerez 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); 
        } 
    } 
}

Pour tester cet exemple, enregistrez ces deux fichiers de classe dans le même répertoire qu’une image nommée « ocean.jpg ». Vous pouvez constater la transformation appliquée à la bitmap d’origine pour qu’elle semble disparaître au loin et pivoter dans l’espace 3D.

Culling

Le culling est un processus qui détermine quelles surfaces d’un objet en trois dimensions ne doivent pas être rendues par le moteur de rendu car elles ne sont pas visibles à partir du point de vue actuel. Dans l’espace 3D, la surface « arrière » d’un objet en trois dimensions n’est pas visible à partir du point de vue.
L’arrière d’un objet 3D n’est pas visible à partir du point de vue.
A.
point de vue

B.
objet 3D

C.
arrière d’un objet en trois dimensions

Par essence, tous les triangles sont systématiquement rendus, quelles que soient leur taille, forme et position. Le culling (élimination) garantit que Flash Player ou AIR rend votre objet 3D correctement. En outre, pour éviter les cycles de rendu superflus, il est parfois souhaitable que le moteur de rendu omette certains triangles. Soit un cube en rotation dans l’espace. A tout moment donné, vous ne voyez jamais plus de trois de ses faces car celles qui ne sont pas visibles font face à l’autre direction, de l’autre côté du cube. Comme ces faces ne sont pas visibles, il est inutile que le moteur de rendu les trace. Sans élimination (culling), Flash Player ou AIR rend les faces avant et arrière.

Certaines faces d’un cube ne sont pas visibles à partir du point de vue actuel.

C’est pourquoi la méthode Graphics.drawTriangles() gère un quatrième paramètre qui permet de définir une valeur de culling :

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

Le paramètre culling correspond à une valeur de la classe d’énumération TriangleCulling : TriangleCulling.NONE , TriangleCulling.POSITIVE et TriangleCulling.NEGATIVE . Ces valeurs sont fonction de la direction du tracé triangulaire définissant la surface de l’objet. L’API ActionScript qui permet de déterminer le culling considère comme acquis que tous les triangles orientés vers l’extérieur d’une forme 3D sont tracés dans le même sens. Le retournement d’une face de triangle entraîne un changement de direction du tracé. A ce point, il est possible de supprimer le triangle du rendu.

Si la valeur de TriangleCulling est définie sur POSITIVE , les triangles dont le tracé a une direction positive (sens horaire) sont supprimés. Si la valeur de TriangleCulling est définie sur NEGATIVE , les triangles dont le tracé a une direction négative (sens antihoraire) sont supprimés. Dans le cas d’un cube, les surfaces orientées vers l’avant ont un tracé à la direction positive et les surfaces orientées vers l’arrière, un tracé à la direction négative.

Cube « déroulé » illustrant le sens du tracé. Lorsque le cube est « enroulé », le sens du tracé de la face arrière est inversé.

Pour comprendre le fonctionnement du processus d’élimination (culling), reprenez l’exemple de la section Mappage des coordonnées UV et définissez le paramètre de culling de la méthode drawTriangles() sur TriangleCulling.NEGATIVE :

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

Vous pouvez constater que la face « arrière » de l’image n’est pas rendue lorsque l’objet pivote.