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에 적용하기 위해 다음과 같이 시도할 수 있다.
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 객체를 가지고 기하학적 변형을 위한 기초는 알 수 있다.
- ActionScript 3.0 Matrix 클래스
- Understanding the Transformation Matrix in Flash 8
- 2D Transformations
- 2D Tranformations – PPT 자료
- Transformation Pipeline
- Affine transformations
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 )만 추가하면 된다. 결국 사진중심을 회전하기 위해 이동후, 회전 및 스케일링을 거친다음에 다시 자신의 위치로 옮겨오는 작업이 추가되는 것이다.
원리를 알면 많이 고민 안하고도 적용할 수 있다는거…
산수 좀 나온다고 거부하면 다음에도 고생한다. ^^
'비공개 > Adobe Flex, ActionScript 3.0' 카테고리의 다른 글
[ActionScript 3.0] TextField의 defaultTextFormat 과 setTextFormat() 차이점 (1) | 2009.05.13 |
---|---|
Flex SDK 3.3 공식배포 (0) | 2009.05.13 |
멀티 파일 업로더 (Multi-file uploader) – “업로드 금지 파일” 등록 기능 추가 (7) | 2009.05.13 |
위젯도 마음대로 못다는 네이버 블로그 (0) | 2009.05.13 |
[Flex/AIR] Custom Event를 만들 때 clone() 함수를 override 해야하는 이유 (0) | 2009.05.13 |