3D : Création et animation d’un cube

2eme partie du tutoriel sur la 3D : Création d’un cube, rotation dans l’espace, trie des faces suivant leurs profondeurs, ajout d’une texture.


La création du cube ne présente aucune difficulté, il suffit d’ajouter les faces une par une en les positionnant correctement dans l’espace.
Pour la rotation du cube, il suffit d’utiliser les propriétés rotationX, rotationY ou rotationZ.
La partie qui peut poser probleme c’est comment savoir si une face est devant ou derrière une autre (celles qui sont censés être cachés ne doivent pas apparaitre devant les autres).
Voici une méthode qui permet de régler cette question :

  • Récupérer la profondeur z de chaque face.
  • Effacer les faces.
  • Trier les faces suivant la valeur z (du plus profond au plus proche).
  • Ajouter les faces dans l’ordre.

Attention : il est préférable que le centre de la face soit réellement son centre et pas un des coins pour éviter les problemes de rendu avec cette méthode.

Pour récuperer la position z d’une face par rapport à la scène, il ne faut pas utiliser sa propriété z mais passer par sa propriété transform :

(cible:DisplayObject).transform.getRelativeMatrix3D(stage).position.z

Faut le savoir, j’ai perdu pas mal de temps avant de trouver l’astuce.


Le SWF :

Le code :

package {
	import flash.display.DisplayObject;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.MouseEvent;
 
 
	[SWF(width = 800, height = 600, backgroundColor = 0x3E3E3E, frameRate = 25)]
 
	/**
	 * Création d'un cube.
	 * rotation du cube dans l'espace.
	 * trie des faces suivant leurs profondeurs.
	 * 
	 * @author Lorenzo
	 */
	public class MainCubeRotation2 extends Sprite {
		// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
		// ———————————————————————————— Déclaration des variables ————————————————————————————
		// ___________________________________________________________________________________
		private var _spCube:Sprite;
		private const TAILLE:Number = 200;
		private var _tbFaces:Array = new Array();
 
 
		// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
		// ——————————————————————————————————— Constructeur ——————————————————————————————————
		// ___________________________________________________________________________________
		public function MainCubeRotation2():void {
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
 
		private function init(e:Event = null):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point
			stage.scaleMode = "noScale";
			stage.align = "LT";
 
			// le sprite contenant les 6 faces représentant le cube
			_spCube = addChild(new Sprite()) as Sprite;
			_spCube.x = stage.stageWidth / 2;
			_spCube.y = stage.stageHeight / 2;
			_spCube.z = 0;
 
			// création et positionnement des 6 faces
			var shFace:Shape = new Shape();
			shFace.graphics.lineStyle(1, 0, 1);
			shFace.graphics.beginFill(0xFF0000, 1);
			shFace.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shFace.graphics.endFill();
			shFace.z = TAILLE/2;
			_spCube.addChild(shFace);
			_tbFaces.push(shFace);
 
			var shArriere:Shape = new Shape();
			shArriere.graphics.lineStyle(1, 0, 1);
			shArriere.graphics.beginFill(0xFF8000, 1);
			shArriere.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shArriere.graphics.endFill();
			shArriere.z = -TAILLE/2;
			_spCube.addChild(shArriere);
			_tbFaces.push(shArriere);
 
			var shGauche:Shape = new Shape();
			shGauche.graphics.lineStyle(1, 0, 1);
			shGauche.graphics.beginFill(0x00FF00, 1);
			shGauche.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shGauche.graphics.endFill();
			shGauche.rotationY = 90;
			shGauche.x = -TAILLE / 2;
			_spCube.addChild(shGauche);
			_tbFaces.push(shGauche);
 
			var shDroite:Shape = new Shape();
			shDroite.graphics.lineStyle(1, 0, 1);
			shDroite.graphics.beginFill(0x008000, 1);
			shDroite.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shDroite.graphics.endFill();
			shDroite.rotationY = 90;
			shDroite.x = TAILLE / 2;
			_spCube.addChild(shDroite);
			_tbFaces.push(shDroite);
 
			var shDessus:Shape = new Shape();
			shDessus.graphics.lineStyle(1, 0, 1);
			shDessus.graphics.beginFill(0x0000FF, 1);
			shDessus.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shDessus.graphics.endFill();
			shDessus.rotationX = 90;
			shDessus.y = -TAILLE / 2;
			_spCube.addChild(shDessus);
			_tbFaces.push(shDessus);
 
			var shBas:Shape = new Shape();
			shBas.graphics.lineStyle(1, 0, 1);
			shBas.graphics.beginFill(0x00FFFF, 1);
			shBas.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shBas.graphics.endFill();
			shBas.rotationX = 90;
			shBas.y = TAILLE / 2;
			_spCube.addChild(shBas);
			_tbFaces.push(shBas);
 
			trierFacesParProfondeur();
			stage.addEventListener(MouseEvent.MOUSE_MOVE, evtStageMouseMove);
		}
 
 
		// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
		// ———————————————————————————— Gestionnaire d'évènements ————————————————————————————
		// ___________________________________________________________________________________
		private function evtStageMouseMove(ev:MouseEvent):void {
			_spCube.rotationX = -(ev.stageY / stage.stageHeight) * 360;
			_spCube.rotationY = -(ev.stageX / stage.stageWidth) * 360;
			trierFacesParProfondeur();
		}
 
 
		// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
		// ————————————————————————————— Déclaration des méthodes ————————————————————————————
		// ___________________________________________________________________________________
		/**
		 * Trier les faces suivant leurs profondeurs (en z) 
		 */
		private function trierFacesParProfondeur():void {
			var pos:Array = new Array();
			// pour chaque Shape représentant une face, récupérer la profondeur de son centre
			for each(var dsp:DisplayObject in _tbFaces) {
				// la bonne maniere pour récuperer la position z de la face par rapport à la scène
				pos.push(dsp.transform.getRelativeMatrix3D(this.stage).position.z);
				// effacer les faces du cube
				_spCube.removeChild(dsp);
			}
			// trier le tableau des positions Z et renvoyer les index pour connaitre l'ordre des faces
			var posZ:Array = pos.sort(Array.NUMERIC | Array.RETURNINDEXEDARRAY | Array.DESCENDING);
			for each(var p:uint in posZ) {
				// ajouter les faces du cube dans le bon ordre (de l'arrière vers l'avant)
				_spCube.addChild(_tbFaces[p]);
			}
		}
 
	}
}


Voici un autre SWF avec une texture appliqué sur chaque face plutôt qu’un simple remplissage.
J’ai aussi ajouté une reconnaissance de la face cliqué sans avoir besoin de convertir les Shape en Sprite et sans leurs ajouter des écouteurs d’évenements souris grace à la méthode getObjectsUnderPoint().
note : getObjectsUnderPoint() renvoie un tableau de tous les DisplayObject d’un DisplayObjectContainer situés sous un point précis, l’ordre des objets dans le tableau renvoyé va du bas vers le haut, donc le dernier élément du tableau est celui situé au dessus de tous les autres.

Le SWF :

Le code :

package {
	import flash.display.Bitmap;
	import flash.display.DisplayObject;
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.geom.ColorTransform;
	import flash.geom.Matrix;
	import flash.geom.Point;
	import flash.text.TextField;
	import flash.text.TextFormat;
	import flash.ui.Keyboard;
 
 
	[SWF(width = 800, height = 600, backgroundColor = 0x3E3E3E, frameRate = 25)]
 
	/**
	 * Creation d'un cube.
	 * Ajout de texture sur les faces.
	 * Rotation du cube dans l'espace.
	 * Trie des faces suivant leurs profondeurs.
	 * Les faces sont cliquables.
	 * 
	 * @author Lorenzo
	 */
	public class MainCubeRotation3 extends Sprite {
		// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
		// ———————————————————————————— Déclaration des variables ————————————————————————————
		// ___________________________________________________________________________________
		private var _spCube:Sprite;
		private const TAILLE:Number = 200;
		private var _tbFaces:Array = new Array();
 
		[Embed(source='../lib/Img2.jpg')]
		private var Img:Class;
 
 
		// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
		// ——————————————————————————————————— Constructeur ——————————————————————————————————
		// ___________________________________________________________________________________
		public function MainCubeRotation3():void {
			if (stage) init();
			else addEventListener(Event.ADDED_TO_STAGE, init);
		}
 
		private function init(e:Event = null):void {
			removeEventListener(Event.ADDED_TO_STAGE, init);
			// entry point
			stage.scaleMode = "noScale";
			stage.align = "LT";
 
 
			var tx:TextField = this.addChild(new TextField()) as TextField;
			tx.defaultTextFormat = new TextFormat("Courier New", 12, 0xFFFFFF);
			tx.text = "Les touches gauche/droite/haut/bas permettent de faire tourner le cube\nLes faces sont cliquables";
			tx.autoSize = "left";
			tx.x = 5;
			tx.y = stage.stageHeight - tx.height;
			tx.mouseEnabled = false;
 
			_spCube = addChild(new Sprite()) as Sprite;
			_spCube.x = stage.stageWidth / 2;
			_spCube.y = stage.stageHeight / 2;
			_spCube.z = 0;
			_spCube.rotationX = 45;
			_spCube.rotationY = 45;
 
			// bitmap utilisé pour texturer les faces
			var btm:Bitmap = new Img() as Bitmap;
			var mat:Matrix = new Matrix();
			mat.createBox(1, 1, 0, -TAILLE / 2, -TAILLE / 2);
 
			var shFace:Shape = new Shape();
			shFace.graphics.lineStyle(1, 0, 1);
			shFace.graphics.beginBitmapFill(btm.bitmapData, mat);
			shFace.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shFace.graphics.endFill();
			shFace.z = TAILLE/2;
			_spCube.addChild(shFace);
			_tbFaces.push(shFace);
 
			var shArriere:Shape = new Shape();
			shArriere.graphics.lineStyle(1, 0, 1);
			shArriere.graphics.beginBitmapFill(btm.bitmapData, mat);
			shArriere.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shArriere.graphics.endFill();
			shArriere.z = -TAILLE/2;
			_spCube.addChild(shArriere);
			_tbFaces.push(shArriere);
 
			var shGauche:Shape = new Shape();
			shGauche.graphics.lineStyle(1, 0, 1);
			shGauche.graphics.beginBitmapFill(btm.bitmapData, mat);
			shGauche.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shGauche.graphics.endFill();
			shGauche.rotationY = 90;
			shGauche.x = -TAILLE / 2;
			_spCube.addChild(shGauche);
			_tbFaces.push(shGauche);
 
			var shDroite:Shape = new Shape();
			shDroite.graphics.lineStyle(1, 0, 1);
			shDroite.graphics.beginBitmapFill(btm.bitmapData, mat);
			shDroite.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shDroite.graphics.endFill();
			shDroite.rotationY = 90;
			shDroite.x = TAILLE / 2;
			_spCube.addChild(shDroite);
			_tbFaces.push(shDroite);
 
			var shDessus:Shape = new Shape();
			shDessus.graphics.lineStyle(1, 0, 1);
			shDessus.graphics.beginBitmapFill(btm.bitmapData, mat);
			shDessus.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shDessus.graphics.endFill();
			shDessus.rotationX = 90;
			shDessus.y = -TAILLE / 2;
			_spCube.addChild(shDessus);
			_tbFaces.push(shDessus);
 
			var shBas:Shape = new Shape();
			shBas.graphics.lineStyle(1, 0, 1);
			shBas.graphics.beginBitmapFill(btm.bitmapData, mat);
			shBas.graphics.drawRect(-TAILLE/2, -TAILLE/2, TAILLE, TAILLE);
			shBas.graphics.endFill();
			shBas.rotationX = 90;
			shBas.y = TAILLE / 2;
			_spCube.addChild(shBas);
			_tbFaces.push(shBas);
 
 
			trierFacesParProfondeur();
 
			stage.addEventListener(KeyboardEvent.KEY_DOWN, evtStageKeyDown);
			stage.addEventListener(MouseEvent.MOUSE_DOWN, evtStageMouseDown);
		}
 
 
		// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
		// ———————————————————————————— Gestionnaire d'évènements ————————————————————————————
		// ___________________________________________________________________________________
		private function evtStageKeyDown(ev:KeyboardEvent):void {
			switch (ev.keyCode) {
				case Keyboard.UP :
					_spCube.rotationX-=2;
					break;
				case Keyboard.DOWN :
					_spCube.rotationX+=2;
					break;
				case Keyboard.LEFT :
					_spCube.rotationY+=2;
					break;
				case Keyboard.RIGHT :
					_spCube.rotationY-=2;
					break;
				default :
					break;
			}
			trierFacesParProfondeur();
		}
 
		private function evtStageMouseDown(ev:MouseEvent):void {
			var tb:Array = getObjectsUnderPoint(new Point(ev.stageX, ev.stageY));
			if ( tb.length ) {
				for each(var dsp:DisplayObject in _tbFaces) {
					if ( dsp == tb[tb.length-1] ) {
						dsp.transform.colorTransform = new ColorTransform(1, 1, 1, 1, -100, -100, -100, 0);
					}else {
						dsp.transform.colorTransform = new ColorTransform(1, 1, 1, 1, 0, 0, 0, 0);
					}
				}
			}
		}
 
 
		// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
		// ————————————————————————————— Déclaration des méthodes ————————————————————————————
		// ___________________________________________________________________________________
		/**
		 * Trier les faces suivant leurs profondeurs (en z) 
		 */
		private function trierFacesParProfondeur():void {
			var pos:Array = new Array();
			// pour chaque Shape représentant une face, recuperer la profondeur de son centre
			for each(var dsp:DisplayObject in _tbFaces) {
				// la bonne maniere pour récuperer la position z de la face
				pos.push(dsp.transform.getRelativeMatrix3D(this.stage).position.z);
				// effacer les faces du cube
				_spCube.removeChild(dsp);
			}
			// trier le tableau des positions Z et renvoyer les index pour connaitre l'ordre des faces
			var posZ:Array = pos.sort(Array.NUMERIC | Array.RETURNINDEXEDARRAY | Array.DESCENDING);
			for each(var p:uint in posZ) {
				// ajouter les faces du cube dans le bon ordre (de l'arriere vers l'avant)
				_spCube.addChild(_tbFaces[p]);
			}
		}
 
	}
}


Cette méthode a le mérite d’être très simple.
Tant que le nombre de face à animer n’est pas trop important (<100?), les performances seront suffisantes pour créer un menu par exemple ou une liste d'images ..etc

Laisser un commentaire

*