[Flex,AIR,ActionScript 3.0]Matrix를 이용해 이동,스케일링,회전,거울효과 적용

Adobe Flash/ActionScript 3.0 2011. 12. 20. 14:18
반응형
출처: http://blog.jidolstar.com/463


ActionScript 3.0에는 Matrix와 Transform 클래스가 있다.

 

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에 적용하기 위해 다음과 같이 시도할 수 있다.

 

1.var content:DisplayObject = new Sprite as DisplayObject;
2.var mat:Matrix = new Matrix();
3.(mat 조작 생략)
4.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에 적용할 수 있을까?

 

01.var content:DisplayObject = new Sprite as DisplayObject;
02.var cx:Number = content.width/2;
03.var cy:Number = content.height/2;
04.var sx:Number = 2;
05.var sy:Number = 3;
06.var theta:Number = 45 * Math.PI/180;
07.var mat:Matrix = new Matrix();
08.mat.translate( -cx, -cy );
09.mat.scale( sx, sy );
10.mat.rotate( theta );
11.content.tranform.matrix = mat;

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

 

01.var content:DisplayObject = new Sprite as DisplayObject;
02.var cx:Number = content.width/2;
03.var cy:Number = content.height/2;
04.var sx:Number = 2;
05.var sy:Number = 3;
06.var theta:Number = 45 * Math.PI/180;
07.var cos:Number = Math.cos( theta );
08.var sin:Number = Math.sin(theta);
09.var mat:Matrix = new Matrix();
10.mat.a = sx * cos;
11.mat.b = sy * sin;
12.mat.c = –sx * sin;
13.mat.d = sy * cos;
14.mat.tx = - sx * cx * cos + sy * cy * sin;
15.mat.ty = - sx * cx * sin - sy * cy * cos;
16.content.tranform.matrix = mat;

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

 

예제 애플리케이션 제작

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


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

 

001.package
002.{
003.import flash.display.DisplayObject;
004.import flash.display.Loader;
005.import flash.display.Sprite;
006.import flash.display.StageScaleMode;
007.import flash.events.Event;
008.import flash.geom.Matrix;
009.import flash.net.URLRequest;
010.import flash.utils.setTimeout;
011. 
012.public class TestSprite extends Sprite
013.{
014.private var container:Sprite;
015.private var image:Loader;
016.private var originalWidth:Number = 0;
017.private var originalHeight:Number = 0;
018.private var _scale:Number = 1;
019.private var _rotation:Number = 0;
020.private var _horizontalMirror:Boolean = false;
021.private var _verticalMirror:Boolean = false;
022.private var isApplyMatrix:Boolean = false;
023. 
024.public function TestSprite()
025.{
026.super();
027. 
028.container = new Sprite;
029.addChild( container );
030. 
031.image = new Loader;
032.container.addChild( image );
033.image.contentLoaderInfo.addEventListener( Event.COMPLETE, onComplete );
034.image.load( new URLRequest( "http://jidolstar.com/blog/wp-content/uploads/2009/03/yaejin.jpg" ) );
035.}
036. 
037.private function onComplete( event:Event ):void
038.{
039.event.target.removeEventListener( Event.COMPLETE, onComplete );
040. 
041.//원본 사진의 크기
042.originalWidth = event.target.content.width;
043.originalHeight = event.target.content.height;
044. 
045.//Matrix적용
046.applyMatrix( container );
047.}
048. 
049.private function applyMatrix( target:DisplayObject ):void
050.{
051.var mat:Matrix = target.transform.matrix;
052.var cos:Number = Math.cos( _rotation * Math.PI/180 );
053.var sin:Number = Math.sin( _rotation * Math.PI/180 );
054.var cx:Number = originalWidth/2;
055.var cy:Number = originalHeight/2;
056. 
057.//단위행렬로 바꿈
058.mat.identity();
059. 
060.//거울 효과 적용
061.if( _horizontalMirror )
062.{
063.mat.a = -1;
064.mat.tx = originalWidth;
065.}
066.if( _verticalMirror )
067.{
068.mat.d = -1;
069.mat.ty = originalHeight;
070.}
071. 
072.//widget의 (0,0)위치 조정
073.mat.translate( -cx, -cy );
074. 
075.//스케일 적용
076.mat.scale( _scale, _scale );   
077. 
078.//회전 적용
079.mat.rotate( _rotation * Math.PI/180 );
080. 
081.//mat.translate( cy, cy );
082. 
083./*
084.//주석부분은 위에 mat.translate(),mat.scale(), mat.rotate()을 호출한것과 동일하게 동작한다. 단 거울효과를 적용했을때는 똑같지 않다. 같은 효과를 내려면 Matrix.concat()을 이용해 행렬곱을 실시하면 되겠다.
085.mat.a = _scale * cos;
086.mat.b = _scale * sin ;
087.mat.c = _scale * sin * -1;
088.mat.d = _scale * cos;
089.mat.tx = -cx * _scale * cos + cy * _scale * sin;
090.mat.ty = -cx * _scale * sin - cy * _scale * cos;
091.*/
092. 
093.//Matrix 적용
094.target.transform.matrix = mat; 
095. 
096.this.width = target.width;
097.this.height = target.height;
098.this.dispatchEvent( new Event( Event.RESIZE ) );   
099. 
100.isApplyMatrix = false;
101.}
102. 
103.public override function set rotation(value:Number):void
104.{
105._rotation = value;
106.if( !isApplyMatrix )
107.{
108.isApplyMatrix = true;
109.setTimeout( applyMatrix, 0, container );
110.}
111.}
112. 
113.public function set scale(value:Number):void
114.{
115._scale = value;
116.if( !isApplyMatrix )
117.{
118.isApplyMatrix = true;
119.setTimeout( applyMatrix, 0, container );
120.}
121.}  
122. 
123.public function set horizontalMirror( value:Boolean ):void
124.{
125._horizontalMirror = value;
126.if( !isApplyMatrix )
127.{
128.isApplyMatrix = true;
129.setTimeout( applyMatrix, 0, container );
130.}
131.}
132. 
133.public function set verticalMirror( value:Boolean ):void
134.{
135._verticalMirror = value;
136.if( !isApplyMatrix )
137.{
138.isApplyMatrix = true;
139.setTimeout( applyMatrix, 0, container );
140.}
141.}
142.}
143.}

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

 

01.<?xml version="1.0" encoding="utf-8"?>
02.<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"creationComplete="init()" backgroundGradientColors="[0,0]">
03.<mx:VBox width="100%" height="100%">
04.<mx:UIComponent id="container" width="100%" height="100%"resize="onResize()"/>
05.<mx:Form>
06.<mx:FormItem label="scale">
07.<mx:HSlider id="sldScale" minimum="0.5" maximum="3" value="1"change="test.scale=sldScale.value" liveDragging="true"/>
08.</mx:FormItem>
09.<mx:FormItem label="rotation">
10.<mx:HSlider id="sldRotation" minimum="0" maximum="360" value="0"change="test.rotation=sldRotation.value" liveDragging="true"/>
11.</mx:FormItem>
12.<mx:FormItem label="horizontal Mirror">
13.<mx:CheckBox id="chHorizontalMirror"change="test.horizontalMirror = chHorizontalMirror.selected"/>
14.</mx:FormItem>
15.<mx:FormItem label="vertical Mirror">
16.<mx:CheckBox id="chVerticalMirror" change="test.verticalMirror = chVerticalMirror.selected"/>
17.</mx:FormItem>
18.</mx:Form>
19.</mx:VBox>
20.<mx:Script>
21.<![CDATA[
22.private var test:TestSprite;
23.private function init():void
24.{
25.test = new TestSprite();
26.container.addChild( test );
27.test.addEventListener( Event.RESIZE, onResizeTest );
28.onResize();
29.}
30. 
31.private function onResize():void
32.{
33.if( test )
34.{
35.test.x = container.width/2;
36.test.y = container.height/2;
37.}
38.}
39. 
40.private function onResizeTest( event:Event ):void
41.{
42.var w:Number = test.width;
43.var h:Number = test.height;
44.var hw:Number = w/2;
45.var hh:Number = h/2;
46.test.graphics.clear();
47.test.graphics.lineStyle( 1, 0xff0000, 1 );
48.test.graphics.drawRect( -hw, -hh, w, h );
49.test.graphics.moveTo( -hw, 0 );
50.test.graphics.lineTo( hw, 0 );
51.test.graphics.moveTo( 0, -hh );
52.test.graphics.lineTo( 0, hh );
53.}
54.]]>
55.</mx:Script>
56.<mx:Style>
57.global
58.{
59.color:#ffffff;
60.}
61. 
62.ToolTip
63.{
64.color:#000000;
65.}
66.</mx:Style>
67.</mx:Application>

 

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

 

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

반응형

'Adobe Flash > ActionScript 3.0' 카테고리의 다른 글

AS3 Array Shuffle  (0) 2011.12.28
중심점 회전  (0) 2011.12.21
BitmapData scroll  (0) 2011.12.09
mx.utils.Base64Encoder  (0) 2011.12.07
opensocial-actionscript-client  (0) 2011.12.07
: