PerspectiveTransform으로 3-D Display Shelf 만들기

작성자 Josh Marinacci, 2008년 10월 20일

JavaFX에 포함되어 있는 PerspectiveTransform은 3-D 효과를 쉽게 생성할 수 있다. 이 샘플은 PerspectiveTransform와 몇가지 계산을 사용하여 3-D display shelf를 만드는 방법을 알려준다.

소스 코드 이해하기

JavaFX에서 가장 흥미로운 것 중에 하나가 PerspectiveTransform이다. (Blur and Glow)와 같은 기능으로 이미지 픽셀의 색을 변형하는 방법보다는 PerspectiveTransform는 사실상 이미지에 다양한 모습으로 왜곡효과를 주고 스크린에서 이미지의 픽셀을 이동한다. PerspectiveTransform는 실질적으로 임의의 사각형에서 또다른 사각형으로 전환한다. 여러분은 이러한 간단한 수학적인 방법으로 쿨(cool) 3-D 효과를 낸다.

Display Shelf는 Item 오브젝트의어떤 수로 나누어지는 DisplayShelf 클래스의 단일 인스턴스로 구성된다. Item 클래스는 3-D효과를 위한 열쇠이다. 3-D효과 적용을 하도록 하는 ItemPerspectiveTransform와 함께 ImageView 노드로 구성된다. 그리고 마우스 이벤트를 잡기위해 직사각형으로 떺어 씌운다.PerspectiveTransform 은 몇가지 계산 변수(lx, rx, uly, ury)에 바운드된다. lx는 왼쪽(left) X, rx는 오른쪽(right) X, uly는 왼쪽의 위(upper left) Y , ury는 오른쪽 위(upper right) Y를 나타낸다. 이 변수들은 사각형의 위쪽 코너(top corners)이다. 여러분은 위쪽 코너(top corners)만 필요하다. 왜나하면 아래쪽 코너(lower corners)는 단지 위쪽 코너(top corners)의 반대점이기 때문이다. 각 변수는 간단한 수학 등식을 사용하여 가운데 angle로 바운드되고, 현재 angle의 싸인(Sine)이나 코사인(cosine)을 기준으로 코너의 위치를 조절한다. 언제든지 angle이 변하면 4가지 변수들도 수정된다. 그때 스크린에서 실제 픽셀 모양을 변경하여 PerspectiveTransform 을 수정한다. 그림 1은 이와 관련된 코드이다.

소스 코드
public class Item extends CustomNode {
    public var position:Integer = 0;
    public var angle = 45.0;
    public var shelf:DisplayShelf;
    public-init var image:Image;

    def width = 200;
    def height = 200;
    def radius = width/2;
    def back = height/10;

    var lx = bind radius - Math.sin(Math.toRadians(angle))*radius;
    var rx = bind radius + Math.sin(Math.toRadians(angle))*radius;
    var uly = bind 0 - Math.cos(Math.toRadians(angle))*back;
    var ury = bind 0 + Math.cos(Math.toRadians(angle))*back;

    function getPT(t:Number):PerspectiveTransform {
        return PerspectiveTransform {
            ulx: lx     uly: uly
            urx: rx     ury: ury
            lrx: rx     lry: height + uly
            llx: lx     lly: height + ury
        }
    }

    override public function create():Node {
        return Group {
            content: [
                ImageView {
                    image: image
                    effect: bind PerspectiveTransform {
                        ulx: lx     uly: uly
                        urx: rx     ury: ury
                        lrx: rx     lry: height + uly
                        llx: lx     lly: height + ury
                    }

                },
                Rectangle {
                    translateX: bind lx
                    width: bind rx-lx
                    height: height
                    fill: Color.rgb(0,0,0,0.0)
                    blocksMouse: true
                    onMousePressed: function(e:MouseEvent) {
                        shelf.shiftToCenter(this);
                    };
                }
            ]
        }
    }

}

그림 1: Item.fx 클래스

위에 ImageView, Rectangle을 주목하자. ImageViewPerspectiveTransform으로 화면에 보여지는 모양을 비튼다. 그러나 경계는 안전하게 남아있다. 이 말은 여러분이 이미지 안쪽을 마우스로 클릭했다면 ImageView 바운드를 계산하는데 사용할 수 없음을 의미합니다. 감지가 정확하지 않을 것이다. 오버레이 Rectangle 은 정확하게 마우스 클릭을 잡기위한 장소를 제공하기 위해 추가되었다. Rectangle의 widthtranslateXRectangle이 이미지와 함께 이동하기 위해 변수 lxrx에 바운드 된다. If you want to see these overlays onscreen change the opacity of the Rectangle.fill to something other than 0.

다중 아이템을 Display Shelf로 조합하기

Item 클래스는 단지 이미지를 일그러지도록 하고, 저장한다. 각 아이템 스크린이나 애니메이션의 위치를 다루진 않는다. 이러한 것을 다루는 것은 DisplayShelf 클래스가 한다. 전체 코드를 설명하지는 않을 것이다. 중요한 부분은 shiftdoLayout 함수이다. DisplayShelf는 3개 영역 스크린을 표현하기 위해 내부적으로 3가지 노드 순서 (left, right, center)를 사용한다.: 그림 2에서 shift 함수는 노드를 하나의 시퀀스에서 방향과 이동 크기에 의존하는 또다른 곳으로 이동한다.

소스코드
public class DisplayShelf extends CustomNode {

    ...
    
    public function shift(offset:Integer):Void {
        if(centerIndex <= 0 and offset > 0 ) {
            return;
        }
        if(centerIndex >= content.size()-1 and offset < 0) {
            return;
        }


        centerIndex -= offset;
        left.content = content[0..centerIndex-1];
        center.content = content[centerIndex];
        right.content = Sequences.<<reverse>>(content[centerIndex+1..content.size()-1]) as Node[];
        doLayout();
    }

그림 2: DisplayShelf 클래스의 shift 함수

shift 함수의 마지막 줄은 아이템의 이전 위치에서 새로운 위치로 이동하도록 하기위해 doLayout 함수를 호출한다. doLayout 애니메이션을 실행하는 것은 KeyFrame 객체의 두가지 배열(출발 위치, 종료 위치)을 만들어낸다. startKeyframes 배열에 현재 위치를 남겨두고, endKeyframes에 새로운 위치를 계산한다. 마지막으로 그림 3에서 보는 것처럼 최종 애니메이션 수행을 하도록 이러한 키 프레임을 사용하여 Timeline을 만든다.

소스 코드
    function doLayout() {

        var startKeyframes:KeyFrame[];
        var endKeyframes:KeyFrame[];
        var duration = 0.5s;


        for(n in content) {
            var it = n as Item;
            insert KeyFrame { time: 0s values: [
                    n.translateX => n.translateX,
                    n.scaleX => n.scaleX,
                    n.scaleY => n.scaleY,
                    it.angle => it.angle,
                    ] } into startKeyframes;
        }

        for(n in left.content) {
            var it = n as Item;
            var newX = -left.content.size()*spacing +  spacing * indexof n + leftOffset;
            insert KeyFrame { time: duration values: [
                    n.translateX => newX,
                    n.scaleX => scaleSmall,
                    n.scaleY => scaleSmall,
                    it.angle => 45
                ] } into endKeyframes;
        }

        for(n in center.content) {
            var it = n as Item;
            insert KeyFrame { time: duration values: [
                    n.translateX => 0,
                    n.scaleX => 1.0,
                    n.scaleY => 1.0,
                    it.angle => 90
                ] } into endKeyframes;
        }

        for(n in right.content) {
            var it = n as Item;
            var newX = right.content.size()*spacing -spacing * indexof n + rightOffset;
            insert KeyFrame { time: duration values: [
                    n.translateX => newX,
                    n.scaleX => scaleSmall,
                    n.scaleY => scaleSmall,
                    it.angle => 135
                ] } into endKeyframes;
        }

        var anim = Timeline {
            keyFrames: [startKeyframes, endKeyframes]
        };
        anim.play();

    }
    ...
}

그림 3: Animating the Items Across the Stage