package
{
	import flash.display.StageAlign;
	import flash.display.StageQuality;
	import flash.display.StageScaleMode;	
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.GraphicsTrianglePath;
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.Matrix3D;
    import flash.geom.PerspectiveProjection;
    import flash.geom.Utils3D;
    import flash.geom.Vector3D;
    import flash.text.TextField;
    import flash.text.TextFieldAutoSize;
    import flash.ui.Keyboard;
    import flash.utils.getTimer;

    [SWF(frameRate=32, backgroundColor=0x000000)]
    
    /**
     * 토성 예제.
     * @author Yongho, Ji
     * @since 2009.07.06
     * @see http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WSF24A5A75-38D6-4a44-BDC6-927A2B123E90.html
     */     
    public class BadSaturn3D extends Sprite
    {
		[Embed(source="saturnmap.jpg")]
		private const SATURN_PACE_IMAGE:Class;
		
		[Embed(source="saturnringcolor.jpg")]
		private const SATURN_RING_IMAGE:Class;
		    	
        // 투영된 토성 표면 Vertex 정보 
        private var saturnFaceProjected:Vector.<Number> = new Vector.<Number>(0, false);
        
        // 투영된 토성 링부분 Vertex 정보 
        private var saturnRingProjected:Vector.<Number> = new Vector.<Number>(0, false);
        
        // 투영
        private var projection:PerspectiveProjection = new PerspectiveProjection();
        
        // World 변환행렬
        private var world:Matrix3D = new Matrix3D;
        
        // View Port (3D 렌더링 대상)
        private var viewport:Shape = new Shape();
        
        // 토성 표면 Mesh 데이타 
        private var saturnFaceMesh:GraphicsTrianglePath;
        
        // 토성 고리 Mesh 데이타 
        private var saturnRingMesh:GraphicsTrianglePath;
        
        // 토성 표면 텍스쳐 
        private var saturnFaceTexture:BitmapData;
        
        // 토성 고리 텍스쳐 
        private var saturnRingTexture:BitmapData;

        // Viewport의 Z축 위치 
        private var viewPortZAxis:Number = 200;
        
        // triangle을 보여줄지 여부  
        private var visibleTriangle:Boolean = true;        
        
        /**
         * 생성자 
         */ 
        public function BadSaturn3D()
        {
            super();

			//화면 설정 
			stage.align = StageAlign.TOP_LEFT;
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.quality = StageQuality.BEST;            
            
            //viewport를 화면의 중심으로 
            viewport.x = stage.stageWidth/2;
            viewport.y = stage.stageHeight/2;
            addChild(viewport);
            
            //projection의 fieldOfView를  60으로 지정 
            projection.fieldOfView = 60;
            
			//Texture
            saturnFaceTexture = (new SATURN_PACE_IMAGE as Bitmap).bitmapData;
			saturnRingTexture = (new SATURN_RING_IMAGE as Bitmap).bitmapData;
            
            //Mesh 데이타 
            saturnFaceMesh = createSphereMesh( 50, 32, 32 );
            saturnRingMesh = createRingMesh( 70, 20, 50 );
            
            //이벤트 처리                             
            stage.addEventListener( Event.RESIZE, onResize );
            stage.addEventListener( Event.ENTER_FRAME, onEnterFrame );
            stage.addEventListener( MouseEvent.MOUSE_DOWN, onMouseEvent );            
            stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyEvent );

			//사용법 표시         
            var textField:TextField = new TextField();
            textField.multiline = true;
            textField.textColor = 0xffffff;
            textField.htmlText = "Show/Hide Triangles : Space Key<br>Rotation : Mouse<br>Zoom In/Out : Left/Right Key<br>Change the field of view : Up/Down Key";
            textField.autoSize = TextFieldAutoSize.LEFT;
            addChild( textField );             
        }
        
        /**
         * 프레임 마다 처리. 
         */ 
        private function onEnterFrame( event:Event ):void
        {
            world.identity(); //단위행렬로 전환 
            world.appendRotation( getTimer() * 0.0027, Vector3D.Z_AXIS );
            world.appendRotation( xRotation, Vector3D.X_AXIS );
            world.appendRotation( yRotation, Vector3D.Y_AXIS );
            world.appendTranslation(0, 0, viewPortZAxis); //이동 
            world.append(projection.toMatrix3D()); //투영 변환 적용 
            
            // mesh 데이터를  투영하여  projected 생성 
            // uvtData도 갱신된다. 갱신되는 데이터는 T값이다. 
            Utils3D.projectVectors( world, saturnFaceMesh.vertices, saturnFaceProjected, saturnFaceMesh.uvtData );
            Utils3D.projectVectors( world, saturnRingMesh.vertices, saturnRingProjected, saturnRingMesh.uvtData );    
            
            viewport.graphics.clear();
            // Triangle 라인을 그림 
            if( visibleTriangle )
            {
                viewport.graphics.lineStyle( 1, 0xff0000, 0.1 );
            }
            
            //Texture 입힌다.
            viewport.graphics.beginBitmapFill( saturnFaceTexture, null, false, true );
            viewport.graphics.drawTriangles( saturnFaceProjected, getSortedIndices(saturnFaceMesh), saturnFaceMesh.uvtData, saturnFaceMesh.culling );                
            viewport.graphics.beginBitmapFill( saturnRingTexture, null, false, true );
            viewport.graphics.drawTriangles( saturnRingProjected, getSortedIndices(saturnRingMesh), saturnRingMesh.uvtData, saturnRingMesh.culling );                
        }
        
       /**
         * 사이즈 변경시 처리  
         */ 
        private function onResize( event:Event ):void
        {
            //viewport는 항상 화면의 중심에 위치하도록 처리 
            viewport.x = stage.stageWidth/2;
            viewport.y = stage.stageHeight/2;    
        }
        
        private var xRotation:Number = -73;
        private var yRotation:Number = 68;
        private var prevX:Number;
        private var prevY:Number;
        /**
         * 마우스 이벤트 - x,y축 회전
         */ 
        private function onMouseEvent( event:MouseEvent ):void
        {
            switch( event.type )
            {
                case MouseEvent.MOUSE_DOWN:
                    stage.addEventListener( MouseEvent.MOUSE_MOVE, onMouseEvent );
                    stage.addEventListener( MouseEvent.MOUSE_UP, onMouseEvent );
                    prevX = mouseX;
                    prevY = mouseY;
                    break;
                case MouseEvent.MOUSE_MOVE:
                    if( event.buttonDown == false )
                    {
                        stage.removeEventListener( MouseEvent.MOUSE_MOVE, onMouseEvent );
                        stage.removeEventListener( MouseEvent.MOUSE_UP, onMouseEvent );
                        break;
                    }
                    var dx:Number = mouseX - prevX;
                    var dy:Number = mouseY - prevY;
                    yRotation += dx;
                    xRotation += dy;
                    //trace( xRotation, yRotation );
                    prevX = mouseX;
                    prevY = mouseY;
                    break;
                case MouseEvent.MOUSE_UP:
                    stage.removeEventListener( MouseEvent.MOUSE_MOVE, onMouseEvent );
                    stage.removeEventListener( MouseEvent.MOUSE_UP, onMouseEvent );
                    break;
            }    
        }
        
        /**
         * 키보드 이벤트 - 확대/축소/삼각형 보이기,안보이기/Field Of View 조정 
         */        
        private function onKeyEvent( event:KeyboardEvent ):void
        {
            trace( event.charCode );
            
            switch( event.keyCode )
            {
                //확대
                case Keyboard.RIGHT:
                    viewPortZAxis += 10;
                    break;
                //축소
                case Keyboard.LEFT:
                    viewPortZAxis -= 10;
                    break;
                //삼각형 보이기/안보기 
                case Keyboard.SPACE:
                    visibleTriangle = !visibleTriangle
                    break;
                //Field Of View 증가
                case Keyboard.UP:
                    if( projection.fieldOfView < 179 )
                    { 
                        projection.fieldOfView++;
                    }                
                    break; 
                //Field Of View 감소 
                case Keyboard.DOWN:
                    if( projection.fieldOfView > 1 )
                    {
                        projection.fieldOfView--;
                    }                
                    break; 
                
            }
        }
                        
    }
}

import flash.display.GraphicsTrianglePath;
import flash.display.TriangleCulling;

/**
 * GraphicsTrianglePath를 기반으로, Z축으로 sort된 인덱스를 돌려준다.
 * 이 작업을 해주어야 z축 깊이에 따라 Triangle이 제대로 그려진다. 
 * @param mesh 정보 
 * @return sort된 index 데이터 
 */
function getSortedIndices( mesh:GraphicsTrianglePath ):Vector.<int> 
{
    var triangles:Array = [];
    var length:uint = mesh.indices.length;
    
    //z축 sort를 위한 기반 제작 
    for ( var i:uint=0; i < length; i += 3 ) 
    {
        var i1:uint = mesh.indices[ i+0 ];
        var i2:uint = mesh.indices[ i+1 ];
        var i3:uint = mesh.indices[ i+2 ];
        var z:Number = Math.min( mesh.uvtData[i1 * 3 + 2], mesh.uvtData[i2 * 3 + 2], mesh.uvtData[i3 * 3 + 2] );
        if (z > 0) 
        { 
            triangles.push({i1:i1, i2:i2, i3:i3, z:z}); 
        }
    }
    
    //z축으로 sort
    triangles = triangles.sortOn("z", Array.NUMERIC);
    
    //sort된 값을 이용해 Vector값 만듬 
    var sortedIndices:Vector.<int> = new Vector.<int>(0, false);
    for each (var triangle:Object in triangles) 
    {
        sortedIndices.push(triangle.i1, triangle.i2, triangle.i3);
    }
    return sortedIndices;
}

/**
 * 구 Mesh 데이터 작성 
 * @param radius 구의 반지름  
 * @param hDiv 수평 방향의 조각 수 
 * @param vDiv 높이 방향의 조각수 
 * @return mesh 데이터 
 */
function createSphereMesh( radius:Number, hDiv:uint, vDiv:uint ):GraphicsTrianglePath
{
    var vertices:Vector.<Number> = new Vector.<Number>( 0, false );
    var indices:Vector.<int> = new Vector.<int>( 0, false );
    var uvtData:Vector.<Number> = new Vector.<Number>( 0, false );
    var mesh:GraphicsTrianglePath = new GraphicsTrianglePath( vertices, indices, uvtData, TriangleCulling.POSITIVE );
    
    for( var i:uint = 0; i <= hDiv; i++ )
    {
        var s1:Number = Math.PI * 2 * i / hDiv;
        for( var j:uint = 0; j <= vDiv; j++ )
        {
            var s2:Number = Math.PI * j / vDiv + Math.PI / 2;
            var cos1:Number = Math.cos( s1 );
            var sin1:Number = Math.sin( s1 );
            var cos2:Number = Math.cos( s2 );
            var sin2:Number = Math.sin( s2 ); 
            var x:Number = radius * cos2 * cos1;
            var y:Number = radius * cos2 * sin1;
            var z:Number = radius * sin2;              
            mesh.vertices.push( x, y, z );     
            mesh.uvtData.push( i / hDiv, j / vDiv, 1 );
            if( j < vDiv && i < hDiv )
            {
                var a:uint =  i      * (vDiv + 1) + j;
                var b:uint = (i + 1) * (vDiv + 1) + j;
                mesh.indices.push(a, a + 1, b, b + 1, b, a + 1); 
            } 
        }
    }    
    
    return mesh;
}

/**
 * 고리 Mesh 데이터 작성 
 * @param radius 고리 안쪽 반지름  
 * @param thickness 고리 두께
 * @param div 고리 조각수 
 * @return mesh 데이터 
 */
function createRingMesh( radius:Number, thickness:Number, div:uint ):GraphicsTrianglePath
{
    var vertices:Vector.<Number> = new Vector.<Number>( 0, false );
    var indices:Vector.<int> = new Vector.<int>( 0, false );
    var uvtData:Vector.<Number> = new Vector.<Number>( 0, false );
    var mesh:GraphicsTrianglePath = new GraphicsTrianglePath( vertices, indices, uvtData, TriangleCulling.NONE );
    var s:Number = 0;
    var x1:Number = radius;
    var x2:Number = radius + thickness;
    var y1:Number = 0;
    var y2:Number = 0;
    var z:Number = 0;          
    var cos:Number;
    var sin:Number;    
    var n:Number = 0;
    
    for( var i:uint = 0; i < div; i++ )
    {
        //Vertices
        mesh.vertices.push( 
            x1, y1, z, 
            x2, y2, z 
        );
        s = Math.PI * 2 * (i + 1) / div;
        cos = Math.cos( s );
        sin = Math.sin( s );
        x1 = radius * cos;
        x2 = (radius + thickness) * cos;
        y1 = radius * sin;
        y2 = (radius + thickness) * sin;
        mesh.vertices.push( 
            x1, y1, z, 
            x2, y2, z 
        );
        //UVT
        mesh.uvtData.push( 
            0,1,1, 
            1,1,1, 
            0,0,1, 
            1,0,1 
        );
        
        //Indices
        n = i * 4;
        mesh.indices.push( n, n+1, n+2, n+2, n+1, n+3 );             
    }    
    
    return mesh;
}