AS3.0で3Dのお勉強(Imageを3次元に配置する)

こちらの本でActionScript3.0で3D表現のお勉強中。

ActionScript 3.0による三次元表現ガイドブック

ActionScript 3.0による三次元表現ガイドブック

Flash CS4向けの本なので、Flash Builderで同じように動かそうとすると、そのまま動かなかったりしたりしなかったりしますが、「ポイントになるクラス」と「考え方」がわかればFlexアプリケーションとしても実装できます。(今のところ*1は。)

上記本で教えてもらったAdobeが提供するFlash CS4のサンプル(ProjectionDragger、サンプルはデベロッパーセンターからダウンロードできます)を元に、フォルダの下にある画像ファイルを読み込んで3次元空間に配置するコンポーネントを作ってみました。

なお、動作を確認した環境は以下の通り。

以下、ソースコードです。

<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009" 
		 xmlns:s="library://ns.adobe.com/flex/spark" 
		 xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="300"
		 enterFrame="group1_enterFrameHandler(event)"
		 resize="group1_resizeHandler(event)">
	<fx:Script>
		<![CDATA[
			import mx.events.FlexEvent;
			import mx.events.ResizeEvent;
			
			import spark.components.Image;
			
			private var images:Vector.<Image> = new Vector.<Image>();
			
			/**
			 * フレームのヘッダ位置が更新されたときに呼ばれる。Flexアプリケーションはデフォルトで24fps。
			 */
			protected function group1_enterFrameHandler(event:Event):void
			{
				if (this.root != null && this.root.transform != null && this.root.transform.perspectiveProjection != null)
				{
					
					// 消失点を現在のマウスカーソルの位置で更新
					var point:Point = new Point(root.mouseX, root.mouseY);
					this.root.transform.perspectiveProjection.projectionCenter = point;
					// ↑↑↑ 今回重要なのはココ!!! ↑↑↑
				}
				
			}
			
			protected function textinput1_enterHandler(event:FlexEvent):void
			{
				try{
					load(new File(textinput.text));
				}catch(error:Error){
					trace(error.getStackTrace());
				}
			}


			/**
			 * 「開く」ボタンが押されたときに呼ばれる
			 */
			protected function button1_clickHandler(event:MouseEvent):void
			{
				
				var directory:File = File.documentsDirectory;
				
				directory.browseForDirectory("画像ファイルが含まれるフォルダを選択");
				
				// ファイル選択イベントのリスナを登録
				directory.addEventListener(Event.SELECT, function(event:Event):void{
					// イベントのターゲットが選択されたファイルなので、`File`型に変換
					var file:File = (event.target as File);
					
					textinput.text = file.nativePath;
					
					load(new File(textinput.text));
					
				});
				
			}
			
			/**
			 * 指定されたディレクトリ下の画像ファイルを読み込む
			 */
			private function load(dir:File):void{
				if(!dir.isDirectory){
					return;
				}
				
				label.text = "画像を読み込み中です...";
				
				for each(var image:Image in images){
					this.removeElement(image);
				}
				images.splice(0, images.length);
				
				dir.addEventListener(FileListEvent.DIRECTORY_LISTING, directoryLlistingComplete);
				dir.getDirectoryListingAsync();
			}
			
			/**
			 * DirectoryListingが完了すると呼ばれる
			 */
			private function directoryLlistingComplete(event:FileListEvent):void{
				
				var array:Array = event.files;
				
				var target:Vector.<File> = new Vector.<File>();
				
				for each(var file:File in array){
					var ext:String = file.extension;
					if(ext == null){
						continue;
					}
					ext = ext.toLocaleLowerCase();
					if(ext == "jpg" || ext == "jpeg" || ext == "png"){
						target.push(file);
					}
					
					if(target.length > 100){
						break;
					}
				}
				
				createImages(target);
				
				label.text = "";
				
			}
			
			/**
			 * 指定された画像ファイル一覧からImageオブジェクトを生成する
			 */
			private function createImages(files:Vector.<File>):void{
				
				var numLayers:int = 10;
				var depthPerLayer:int = 100;
				
				for each(var file:File in files){
					var i:int = Math.random() * 10;
					
					var x:Number = Math.random() * stage.width;
					var y:Number = Math.random() * stage.height;
					var z:Number = (numLayers - i) * depthPerLayer;
					
					images.push(createImage(x,y,z,file));
				}
				
				//降順のソート(zが大きい順)
				images.sort(function(imgA:Image, imgB:Image):int{
					
					//Aが先
					if (imgA.z < imgB.z)
					{
						return -1;	
					}
					//Bが先
					if (imgA.z > imgB.z)
					{
						return 1;
					}
					
					return 0;
				});
				
				for each(var image:Image in images){
					this.addElementAt(image, 0);
				}
				
			}

			/**
			 * Imageオブジェクトを生成
			 */
			private function createImage(x:Number, y:Number, z:Number, file:File):Image
			{
				var image:Image = new Image();
				image.x = x;
				image.y = y;
				image.z = z;
				image.source = file.url;
				return image;
			}
			

			/**
			 * このコンポーネントの大きさが変わった際に、内部のImageオブジェクトの位置を更新する
			 */
			protected function group1_resizeHandler(event:ResizeEvent):void
			{
				
				var hRate:Number = this.height / event.oldHeight;
				var wRate:Number = this.width / event.oldWidth;
				
				for each(var image:Image in images){
					image.x = image.x * wRate;
					image.y = image.y * hRate;
				}
			}

		]]>
	</fx:Script>
	<s:TextInput id="textinput" left="10" right="88" bottom="10" enter="textinput1_enterHandler(event)"/>
	<s:Button right="10" bottom="10" label="開く..." click="button1_clickHandler(event)"/>
	<s:Label id="label" x="10" y="10"/>

</s:Group>

*1:まだ2章しか読み終わってないけどね...