ActionScript 3.0에는 MatrixTransform 클래스가 있다.

 

Matrix는 일종의 변환행렬(Transformation Matrix)이다. 3×3 행렬로 이동(translation), 확대/축소(scaling), 회전(rotation), 기울이기(shearing)등을 구현할 수 있다. Papervision3D나 Away3D와 같이 2D화면에 3D 효과가 가능했던 것은 Bitamp과 Matrix가 있기 때문이다. 이들 클래스를 어떻게 잘 조작하느냐에 따라 풍성한 화면효과를 구현할 수 있다.

 

Transform은 DisplayObject 계열의 객체에 만들어진 Matrix를 적용하는 역할을 한다. 즉 Matrix는 이동/회전등의 변환수단으로 작용하고 실제 이 Matrix를 이용해서 DisplayObject에 적용하는 것은 Transform인 것이다.

 

DisplayObject에는 tranform 속성이 있고 transform에는 matrix 속성이 있다. 그러므로 만들어진 Matrix를 DisplayObject에 적용하기 위해 다음과 같이 시도할 수 있다.

 

var content:DisplayObject = new Sprite as DisplayObject;
var mat:Matrix = new Matrix();
(mat 조작 생략)
content.tranform.matrix = mat;

 

 

위와 같이 하는 것만으로도 이동,확대/축소,회전,shearing,거울효과등을 적용할 수 있게 된다.

그럼 어떻게 Matrix를 만들어야 원하는 동작을 만들어낼 수 있을까? 다행히도 Matrix는 사용자가 조작하기 아주 쉽게 만들어져 있다.

 

회전을 하려면 Matrix의 rotate( 라디안각도 ), 이동을 하려면 Matrix의 translate( dx, dy ), 확대/축소하려면 Matrix의 scale( sx, sy ) 함수를 사용하면 된다.

 

보통 이 Matrix를 이용하지 않고도 DisplayObject의 rotation, x, y, scaleX, scaleY 속성을 사용해도 될때가 있다. 사릴 이 속성들은 결국 Matrix로 구현된다. Matrix가 다루기 어렵기 때문에 쉬운 사용을 위해 기본적인 인터페이스는 DisplayObject에 만들어준 것 뿐이다. 하지만 이 외에 사진의 중심으로 회전하던가 거울효과를 적용시키던가 shearing과 같은 효과를 주려면 이들 속성만 가지고는 해결할 수 있는 방법이 없거나 있다고 하더라도 비효율적인 방법일 소지가 많다. 그러므로 고급적으로 DisplayObject를 가공하려면 Matrix의 사용방법과 그 원리에 대해서 익숙해져야 한다.

 

Matrix는 3×3로 구성된다고 했다. 2D인데 3×3 행렬을 이용하는 이유는 이동(translation)이 포함되어 있기 때문이다.

 

일단 전반적인 지식은 아래 링크들을 참고하기 바란다. 아래 내용만 잘 알아도 DisaplyObject 객체를 가지고 기하학적 변형을 위한 기초는 알 수 있다.

 

DisplayObject 객체의 중심을 그의 부모 (0,0)점에 위치하고 회전 및 확대/축소

DisplayObject객체 중심을 객체의 좌측상단점으로 이동하는 행렬 T, 회전행렬 R, 확대/축소행렬 S라고 하자.

 

DisplayObject는 항상 좌측상단이 기준점이 된다. 그러므로 DisplayObject의 중심점 이동->회전->확대/축소를 적용하면 되겠다. 그러므로 이들을 모두 적용할 수 있는 행렬은 다음과 같다.

 

 

M = S x R x T = R x S x T

 

더 명확히 표현하자면 아래와 같다. (아래식에서 각각의 변환행렬을 곱한 결과는 그 아래 실제 결과와 다르게 나왔다. 실제결과를 도출하기 위한 뭔가 다른 설정이 있는 것 같은데 본인은 발견하지 못했다. 혹시 아는분 댓글 부탁한다.)

 

 

 

여기서 θ는 회전각도 radian값이고 sx와 sy는 각각 x축, y축 확대/축소 비율이다. cx와 cy는 DisplayObject 객체의 중심좌표값이다.

 

A=[x,y,1]값을 변환을 거쳐서 나온 결과 좌표값을 A’=[x’,y’,1]이라고 한다면 다음 관계가 성립한다.

 

A’= M x A

 

 

결국 중요한 것은 변환행렬 M을 만들어 내는 일이다.

 

그럼 위 행렬을 어떻게 DisplayObject에 적용할 수 있을까?

 

var content:DisplayObject = new Sprite as DisplayObject;
var cx:Number = content.width/2;
var cy:Number = content.height/2;
var sx:Number = 2;
var sy:Number = 3;
var theta:Number = 45 * Math.PI/180;
var mat:Matrix = new Matrix();
mat.translate( -cx, -cy );
mat.scale( sx, sy );
mat.rotate( theta );
content.tranform.matrix = mat;

위 코드처럼 하면 변환행렬 M을 DisplayObject에 적용한 것과 같다. 다음과 같이 해도 동일한 동작을 하게 된다.

 

var content:DisplayObject = new Sprite as DisplayObject;
var cx:Number = content.width/2;
var cy:Number = content.height/2;
var sx:Number = 2;
var sy:Number = 3;
var theta:Number = 45 * Math.PI/180;
var cos:Number = Math.cos( theta );
var sin:Number = Math.sin(theta);
var mat:Matrix = new Matrix();
mat.a = sx * cos;
mat.b = sy * sin;
mat.c = –sx * sin;
mat.d = sy * cos;
mat.tx = - sx * cx * cos + sy * cy * sin;
mat.ty = - sx * cx * sin - sy * cy * cos;
content.tranform.matrix = mat;

이런 원리를 잘 알아두면 앞으로 DisplayObject의 기하학적 변형을 위한 방법을 익히는 것 뿐아니라 속도향상에도 도움이 될 수 있겠다.

 

예제 애플리케이션 제작

위 설명을 토대로 사진 중심으로 회전, 확대/축소등이 가능한 DisplayObject 객체를 만들고 테스트 해볼 수 있는 예제를 만들어보자.


아래는 만들어진 테스트용 애플리케이션이다. (사진 주인공은 제 딸 예진이  입니다. ^^)

 (서버를 바꾼뒤 이미지 로드를 실패하고 있습니다. 아래 소스는 정상적으로 동작하는 것입니다.)

 

 

아래 클래스는 이동/스케일링/회전/거울효과를 테스트 하기 위한 것이다. 위에서 다 설명했으므로 특별히 분석은 안하도록 하겠다.

 

package
{
	import flash.display.DisplayObject;
	import flash.display.Loader;
	import flash.display.Sprite;
	import flash.display.StageScaleMode;
	import flash.events.Event;
	import flash.geom.Matrix;
	import flash.net.URLRequest;
	import flash.utils.setTimeout;

	public class TestSprite extends Sprite
	{
		private var container:Sprite;
		private var image:Loader;
		private var originalWidth:Number = 0;
		private var originalHeight:Number = 0;
		private var _scale:Number = 1;
		private var _rotation:Number = 0;
		private var _horizontalMirror:Boolean = false;
		private var _verticalMirror:Boolean = false;
		private var isApplyMatrix:Boolean = false;

		public function TestSprite()
		{
			super();

			container = new Sprite;
			addChild( container );

			image = new Loader;
			container.addChild( image );
			image.contentLoaderInfo.addEventListener( Event.COMPLETE, onComplete );
			image.load( new URLRequest( "http://jidolstar.com/blog/wp-content/uploads/2009/03/yaejin.jpg" ) );
		}

		private function onComplete( event:Event ):void
		{
			event.target.removeEventListener( Event.COMPLETE, onComplete );

			//원본 사진의 크기
			originalWidth = event.target.content.width;
			originalHeight = event.target.content.height;

			//Matrix적용
			applyMatrix( container );
		}

		private function applyMatrix( target:DisplayObject ):void
		{
			var mat:Matrix = target.transform.matrix;
			var cos:Number = Math.cos( _rotation * Math.PI/180 );
			var sin:Number = Math.sin( _rotation * Math.PI/180 );
			var cx:Number = originalWidth/2;
			var cy:Number = originalHeight/2;

			//단위행렬로 바꿈
			mat.identity();

			//거울 효과 적용
			if( _horizontalMirror )
			{
				mat.a = -1;
				mat.tx = originalWidth;
			}
			if( _verticalMirror )
			{
				mat.d = -1;
				mat.ty = originalHeight;
			}

			//widget의 (0,0)위치 조정
			mat.translate( -cx, -cy );

			//스케일 적용
			mat.scale( _scale, _scale );	

			//회전 적용
			mat.rotate( _rotation * Math.PI/180 );

			//mat.translate( cy, cy );

			/*
			//주석부분은 위에 mat.translate(),mat.scale(), mat.rotate()을 호출한것과 동일하게 동작한다. 단 거울효과를 적용했을때는 똑같지 않다. 같은 효과를 내려면 Matrix.concat()을 이용해 행렬곱을 실시하면 되겠다.
 			mat.a = _scale * cos;
			mat.b = _scale * sin ;
			mat.c = _scale * sin * -1;
			mat.d = _scale * cos;
			mat.tx = -cx * _scale * cos + cy * _scale * sin;
			mat.ty = -cx * _scale * sin - cy * _scale * cos;
			*/

			//Matrix 적용
			target.transform.matrix = mat;	

			this.width = target.width;
			this.height = target.height;
			this.dispatchEvent( new Event( Event.RESIZE ) );	

			isApplyMatrix = false;
		}

		public override function set rotation(value:Number):void
		{
			_rotation = value;
			if( !isApplyMatrix )
			{
				isApplyMatrix = true;
				setTimeout( applyMatrix, 0, container );
			}
		} 

		public function set scale(value:Number):void
		{
			_scale = value;
			if( !isApplyMatrix )
			{
				isApplyMatrix = true;
				setTimeout( applyMatrix, 0, container );
			}
		}	

		public function set horizontalMirror( value:Boolean ):void
		{
			_horizontalMirror = value;
			if( !isApplyMatrix )
			{
				isApplyMatrix = true;
				setTimeout( applyMatrix, 0, container );
			}
		}

		public function set verticalMirror( value:Boolean ):void
		{
			_verticalMirror = value;
			if( !isApplyMatrix )
			{
				isApplyMatrix = true;
				setTimeout( applyMatrix, 0, container );
			}
		}
	}
}

아래는 위에서 정의한 TestSprite를 사용하는 Flex Application이다.

 

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" creationComplete="init()" backgroundGradientColors="[0,0]">
	<mx:VBox width="100%" height="100%">
		<mx:UIComponent id="container" width="100%" height="100%" resize="onResize()"/>
		<mx:Form>
			<mx:FormItem label="scale">
				<mx:HSlider id="sldScale" minimum="0.5" maximum="3" value="1" change="test.scale=sldScale.value" liveDragging="true"/>
			</mx:FormItem>
			<mx:FormItem label="rotation">
				<mx:HSlider id="sldRotation" minimum="0" maximum="360" value="0" change="test.rotation=sldRotation.value" liveDragging="true"/>
			</mx:FormItem>
			<mx:FormItem label="horizontal Mirror">
				<mx:CheckBox id="chHorizontalMirror" change="test.horizontalMirror = chHorizontalMirror.selected"/>
			</mx:FormItem>
			<mx:FormItem label="vertical Mirror">
				<mx:CheckBox id="chVerticalMirror" change="test.verticalMirror = chVerticalMirror.selected"/>
			</mx:FormItem>
		</mx:Form>
	</mx:VBox>
	<mx:Script>
		<![CDATA[
			private var test:TestSprite;
			private function init():void
			{
				test = new TestSprite();
				container.addChild( test );
				test.addEventListener( Event.RESIZE, onResizeTest );
				onResize();
			}

			private function onResize():void
			{
				if( test )
				{
					test.x = container.width/2;
					test.y = container.height/2;
				}
			}

			private function onResizeTest( event:Event ):void
			{
				var w:Number = test.width;
				var h:Number = test.height;
				var hw:Number = w/2;
				var hh:Number = h/2;
				test.graphics.clear();
				test.graphics.lineStyle( 1, 0xff0000, 1 );
				test.graphics.drawRect( -hw, -hh, w, h );
				test.graphics.moveTo( -hw, 0 );
				test.graphics.lineTo( hw, 0 );
				test.graphics.moveTo( 0, -hh );
				test.graphics.lineTo( 0, hh );
			}
		]]>
	</mx:Script>
	<mx:Style>
		global
		{
			color:#ffffff;
		}

		ToolTip
		{
			color:#000000;
		}
	</mx:Style>
</mx:Application>

 

위에서 보여준 것과 달리 좌측상단을 기준으로 하고 사진중심만 회전하고 싶은 경우에는 TestSprite 클래스에서 mat.rotate() 부분 아래에 mat.translate( cx, cy )만 추가하면 된다. 결국 사진중심을 회전하기 위해 이동후, 회전 및 스케일링을 거친다음에 다시 자신의 위치로 옮겨오는 작업이 추가되는 것이다.

 

원리를 알면 많이 고민 안하고도 적용할 수 있다는거…
산수 좀 나온다고 거부하면 다음에도 고생한다. ^^

 

 

 

+ Recent posts