Matrix Tutorial

by 9re
♥12 | Line 345 | Modified 2009-10-28 14:06:15 | MIT License | (replaced)
play

Related images

ActionScript3 source code

/**
 * Copyright 9re ( http://wonderfl.net/user/9re )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/xO2m
 */

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
 paddingBottom="0" paddingLeft="0" paddingRight="0" paddingTop="0"
 creationComplete="init();" frameRate="30">
	 <mx:Script><![CDATA[
	import flash.display.Bitmap;
	import flash.display.BitmapData;
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.display.TriangleCulling;
	import flash.events.Event;
	import flash.geom.ColorTransform;
	import flash.geom.Matrix;
	import flash.net.FileFilter;
	import flash.net.FileReference;
	import flash.net.URLRequest;
	import flash.system.LoaderContext;
	import mx.collections.ArrayCollection;
	import mx.controls.TextInput;
	import mx.core.UIComponent;
	public var fileRef:FileReference;

	private const DEFAULT_IMAGE:String = "http://assets.wonderfl.net/images/related_images/d/d0/d028/d028067724d74b7aba05141bc958aae5c16ffb08";

	private var _bmd:BitmapData;
	private var _ldr:Loader = new Loader;
	private var _clearCTFM:ColorTransform = new ColorTransform(1, 1, 1, 0);
	private var _init:Boolean;
	private var _lock:Boolean = false;
	private var _currentMatrix:Matrix = new Matrix;
	private var _animationData:Vector.<Matrix>;
	 
	private function init():void {
		_animationData = new Vector.<Matrix>;
		makeSampleData();
		clearTexts();
		_init = true;

		_bmd = new BitmapData(300, 465);
		var sp:Sprite = new Sprite;
		sp.addChild(new Bitmap(_bmd));
		var ui:UIComponent = new UIComponent;
		ui.addChild(sp);
		canvas.addChild(ui);
		_ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoaded);
		// set checkPolicyFile true when you use images placed
		// under assts.wonderfl.net
		_ldr.load(new URLRequest(DEFAULT_IMAGE), new LoaderContext(true));
	}
	
	private function makeSampleData():void
	{
		var m:Matrix = new Matrix;
		m = new Matrix;
		m.translate( -50, -50);
		tfTranslateX.text = "-50";
		tfTranslateY.text = "-50";
		generateAnimation("translate", -50, -50);
		appendMatrix(m);
		
		tfScaleX.text = "2";
		tfScaleY.text = "2";
		generateAnimation("scale", 2, 2);
		m = new Matrix;
		m.scale(2, 2);
		appendMatrix(m);
		
		tfRotation.text = "Math.PI*4/3";
		generateAnimation("rotate", Math.PI * 4 / 3);
		m = new Matrix;
		m.rotate(Math.PI * 4 / 3);
		appendMatrix(m);
		
		tfTranslateX.text = "100";
		tfTranslateY.text = "100";
		generateAnimation("translate", 100, 100);
		m = new Matrix;
		m.translate(100, 100);
		appendMatrix(m);
	}
	 
	private function onImageLoaded($event:Event):void 
	{
		_ldr.contentLoaderInfo.removeEventListener(Event.COMPLETE, onImageLoaded);
		draw();
	}
	 
	private function draw():void
	{
		if (!_init) return;

		script.text = "var mat:Matrix = new Matrix();\n";
		var item:Object;
		for (var i:int = 0; i < dpMatrixData.length; ++i) {
			item = dpMatrixData.getItemAt(i);
			script.text += "mat." + item.transform + ";\n";
		}
		_bmd.colorTransform(_bmd.rect, _clearCTFM);
		_bmd.draw(_ldr, _currentMatrix);
		script.text += "bitmapData.draw(loader, mat);";
	}
	 
	 private function changeImage():void {
		fileRef = new FileReference;
		fileRef.browse([new FileFilter("Images(*.jpg, *.jpeg, *.png)", "*.jpg;*.jpeg;*.png;")]);
		fileRef.addEventListener(Event.SELECT, fileSelected);
	 }
	 
	 private function fileSelected($event:Event):void 
	 {
		fileRef.addEventListener(Event.COMPLETE, onFileLoaded);
		fileRef.load();
	 }
	 
	 private function onFileLoaded(e:Event):void 
	 {
		fileRef.removeEventListener(Event.COMPLETE, onFileLoaded);
		fileRef.removeEventListener(Event.SELECT, fileSelected);
		_ldr = new Loader;
		_ldr.contentLoaderInfo.addEventListener(Event.COMPLETE, onImageLoaded);
		_ldr.loadBytes(fileRef.data);
	 }
	 
	 private function converToNumber($textInput:TextInput):Number {
		var original:String = $textInput.text.replace(/ /g, "");
		var n:Number = NaN;
		try {
			n = parseFloat(parse(original));
		} catch(e:Error) {
			
		}
		
		if (isNaN(n)) {
			$textInput.text = "0";
			n = 0;
		} else {
			$textInput.text = original;
		}
			
		return n;
	 }
	 
	 private function clearTexts():void {
		tfRotation.text = "";
		tfScaleX.text = "";
		tfScaleY.text = "";
		tfTranslateX.text = "";
		tfTranslateY.text = "";
	 }
	 
	 private function clearMatrix():void {
		if (_lock) return;
		 
		_currentMatrix = new Matrix;
		_animationData.length = 0;
		clearTexts();
		cbTransform.selectedIndex = 0;
		dpMatrixData.removeAll();
	 }
	 
	 public function appendMatrix($matrix:Matrix):void {
		// skip if matrix is an eigen matix
		if ($matrix.a == 1 && $matrix.b == 0 && $matrix.c == 0 && $matrix.d == 1 && $matrix.tx == 0 && $matrix.ty == 0)
			return;
		var item:Object = { };
		if ($matrix.b != 0 || $matrix.c != 0) { // rotation matrix
			item.transform = "rotate(" + tfRotation.text + ")";
		} else if ($matrix.tx != 0 || $matrix.ty != 0) { // translation matrix
			item.transform = "translate(" + tfTranslateX.text + ", " + tfTranslateY.text + ")";
		} else { // scaling matrix
			item.transform = "scale(" + tfScaleX.text + ", " + tfScaleY.text + ")";
		}
		
		_currentMatrix.concat($matrix);
		dpMatrixData.addItem(item);
	 }
	 
	 private function apply():void {
		if (_lock) return;
		
		_lock = true; 
		
		var a:Number, b:Number;
		var m:Matrix = new Matrix;
		switch (cbTransform.selectedIndex) {
		case 0: // rotation
			a = converToNumber(tfRotation);
			m.rotate(a);
			generateAnimation("rotate", a);
			break;
		case 1: // scale
			a = converToNumber(tfScaleX);
			b = converToNumber(tfScaleY);
			m.scale(a, b);
			generateAnimation("scale", a, b);
			break;
		case 2: // translate
			a = converToNumber(tfTranslateX);
			b = converToNumber(tfTranslateY);
			m.translate(a, b);
			generateAnimation("translate", a, b);
			break;
		}
		
		appendMatrix(m);
		_lock = false;
	 }
	 
	private function generateAnimation($operation:String, $a:Number, $b:Number = 0):void {
		var m:Matrix, c:Matrix;
		var t:Number, u:Number;
		for (var i:int = 0; i < 30; ++i) {
			m = new Matrix;
			c = _currentMatrix.clone();
			t = (i + 1) / 30;
			u = 1 - t;
			switch($operation) {
			case "rotate":
				m.rotate(t * $a);
				c.concat(m);
				_animationData.push(c);
				break;
			case "scale":
				m.scale(u + t * $a, u + t * $b);
				c.concat(m);
				_animationData.push(c);
				break;
			case "translate":
				m.translate(u + t * $a, u + t * $b);
				c.concat(m);
				_animationData.push(c);
				break;
			}
		}
	}
	 
	private function startAnimation():void {
		_lock = true;
		trace("startAnimation", _animationData.length);
		
		addEventListener(Event.ENTER_FRAME, (
			function():Function {
				var i:int = 0;
				
				return function ($event:Event):void {
					if (i == _animationData.length) {
						removeEventListener(Event.ENTER_FRAME, arguments.callee);
						_lock = false;
					} else {
						if (i % 30 == 0)
							dg.selectedIndex = int(Math.floor(i / 30));
							
						_bmd.colorTransform(_bmd.rect, _clearCTFM);
						_bmd.draw(_ldr, _animationData[i++]);
					}
				}
			}
		)());
	}
	
	// Math.PIと小数の演算が出来るように改造
	// http://d.hatena.ne.jp/nitoyon/20090128/as3_simple_parser
	// 10分で書ける、お手軽パーザーを AS3 で
	// Simple Recursive Descent Parsing 
	// see also: http://fxp.hp.infoseek.co.jp/arti/parser.html 
	 private function parse($expr:String):String {
		var pos:int = 0;
		var str:String = $expr.replace(/ /g, "").replace(/Math.PI/g, Math.PI.toString());
		// Expr = Term { (+|-) Term}
		var expr:Function = function():Number{ 
			var ret:Number = term(); 
			while(true){ 
				switch(str.charAt(pos)){ 
					case "+": pos++; ret += term(); break; 
					case "-": pos++; ret -= term(); break; 
					default:  return ret; 
				} 
			} 
			return 0; // never comes here 
		};
		// Term = Fact { (*|/) Fact} 
		var term:Function = function():Number{ 
			var ret:Number = fact(); 
			while(true){ 
				switch(str.charAt(pos)){ 
					case "*": pos++; ret *= fact(); break; 
					case "/": pos++; ret /= fact(); break; 
					default:  return ret; 
				} 
			} 
			return 0; // never comes here 
		};
		// Fact = ( Expr ) | - Fact | number 
		var fact:Function = function():Number{ 
			var ret:Number; 
			var m:Array; 
			if((m = str.substr(pos).match(/^(\d+(\.\d+)?)/))){ 
				pos += m[1].length; 
				return parseFloat(m[1]); 
			} 
			else if(str.charAt(pos) == "-"){ 
				pos++; 
				return -fact(); 
			} 
			else if(str.charAt(pos) == "("){ 
				pos++; 
				ret = expr(); 
				if(str.charAt(pos) != ")") throw new Error("No match for )"); 
				pos++; 
				return ret; 
			} 
			throw new Error("invalid format"); 
		};
		
		return expr().toString();
	 }
	 ]]></mx:Script>
	 <mx:ArrayCollection id="dpMatrixData" collectionChange="draw();" />
	 <mx:ArrayCollection id="transforms">
		 <mx:Object label="rotate" angle="0" index="0" />
		 <mx:Object label="scale" scaleX="1" scaleY="1" index="1" />
		 <mx:Object label="translate" translateX="0" translateY="0" index="2" />
	 </mx:ArrayCollection>
	 <mx:HDividedBox width="100%" height="100%" resizeToContent="true">
		 <mx:VBox height="100%">
			<mx:HBox>
				<mx:Button label="clear matrix" click="clearMatrix();" />
				<mx:Button label="change image" click="changeImage();" />
			</mx:HBox>
			<mx:Spacer height="5" />
			<mx:TabNavigator width="100%" paddingTop="0" paddingBottom="0" creationPolicy="all">
				<mx:VBox label="transforms" height="100%">
					<mx:DataGrid id="dg" dataProvider="{dpMatrixData}" width="100%" height="100%" sortableColumns="false" />
				</mx:VBox>
				<mx:VBox label="view script" height="100%">
					<mx:TextArea id="script" width="100%" height="100%" />
				</mx:VBox>
			</mx:TabNavigator>
			<mx:VBox height="100%">
				 <mx:VBox>
					 <mx:Spacer height="5" />
					 <mx:HBox>
						<mx:Label text="transform" />
						<mx:ComboBox id="cbTransform" dataProvider="{transforms}" />
					 </mx:HBox>
					 <mx:Spacer height="5" />
					 <mx:HBox>
						<mx:Label text="apply transform:" /> 
						<mx:Label text="{cbTransform.selectedItem.label}" />
					 </mx:HBox>
					 <mx:ViewStack creationPolicy="all" resizeToContent="true" selectedIndex="{cbTransform.selectedIndex}">
						 <mx:HBox>
							 <mx:Label text="rotate" />
							 <mx:VBox>
								<mx:TextInput id="tfRotation" />
								<mx:Text text="e.g. Math.PI / 3" />
							 </mx:VBox>
						 </mx:HBox>
						 <mx:VBox>
							 <mx:HBox>
								 <mx:Label text="scaleX" />
								 <mx:TextInput id="tfScaleX" />
							 </mx:HBox>
							 <mx:HBox>
								 <mx:Label text="scaleY" />
								 <mx:TextInput id="tfScaleY" />
							 </mx:HBox>
						 </mx:VBox>
						 <mx:VBox>
							 <mx:HBox>
								 <mx:Label text="translateX" />
								 <mx:TextInput id="tfTranslateX" />
							 </mx:HBox>
							 <mx:HBox>
								 <mx:Label text="translateY" />
								 <mx:TextInput id="tfTranslateY" />
							 </mx:HBox>
						 </mx:VBox>
					 </mx:ViewStack>
					 <mx:Spacer height="5" />
					 <mx:HBox>
						<mx:Button label="apply" click="apply();" />
						<mx:Button label="show animation" click="startAnimation();" />
					</mx:HBox>
				 </mx:VBox>
			 </mx:VBox>
		 </mx:VBox>
		 <mx:Canvas id="canvas" />
	 </mx:HDividedBox>
</mx:Application>