ADOBE/ ActionScript

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

AlrepondTech 2020. 9. 20. 00:44
반응형

 

 

 

=================================

=================================

=================================

 

 

 

 

 

 

 

 

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

 

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 )만 추가하면 된다. 결국 사진중심을 회전하기 위해 이동후, 회전 및 스케일링을 거친다음에 다시 자신의 위치로 옮겨오는 작업이 추가되는 것이다.

 

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



출처: http://smilejsu.tistory.com/288 [{ Unity3D }]

 

 

 

 

=================================

=================================

=================================

 

 

 

 

반응형