Forked from: jidolstar's Geodesic Sphere using drawTriangle diff:1 forked from: Geodesic Sphere using drawTriangle dotf forked:0favorite:0lines:273license : MIT License modified : 2009-11-13 16:14:42 Embed Tweet // forked from jidolstar's Geodesic Sphere using drawTriangle package { import flash.display.*; import flash.events.*; import flash.geom.*; import flash.text.*; import flash.ui.*; [SWF(frameRate=24, backgroundColor=0x000000)] /** * Geodesic Sphere using drawTriangle() * @author Yongho Ji (jidolstar@gmail.com) * @since 2009.09.16 */ public class GeodesicSphereUsingDrawTriangle extends Sprite { //투영된 Vectex 정보 private var projected:Vector.<Number>; //World 변환행렬 private var world:Matrix3D; //투영을 위한 변환행렬 private var projection:Matrix3D; //Mesh 데이터 private var mesh:GraphicsTrianglePath; //ViewPort (3D 렌더링 대상) private var viewport:Shape; //반경 private var radius:Number = 200; //나눌수 private var fractures:int = 15; //회전각 private var zRotation:Number = 0; private var xRotation:Number = -125; //Z축 위치 private var zPosition:Number = -10; //Temp : 이전값 private var prevX:Number; private var prevY:Number; //Temp : 마우스로 회전시 private var rotating:Boolean = false; private var fracturesChanged:Boolean = false; private var rendering:Boolean = true; /** * 생성자 */ public function GeodesicSphereUsingDrawTriangle() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; //Viewport addChild( viewport = new Shape() ); //투영 변환 행렬 var p:PerspectiveProjection = new PerspectiveProjection(); p.fieldOfView = 30; projection = p.toMatrix3D(); //World 변환행렬 world = new Matrix3D(); //Mesh 데이타 //radius = Utils3D.projectVector( projection, new Vector3D(1.0,0,1.0) ).x/4; zPosition = radius; mesh = createGeodesicSphereMesh( radius, fractures ); projected = new Vector.<Number>(0,false); //이벤트 핸들러 등록 stage.addEventListener( Event.ENTER_FRAME, render ); stage.addEventListener( Event.RESIZE, arrange ); stage.addEventListener( MouseEvent.MOUSE_DOWN, onMouseEvent ); stage.addEventListener( MouseEvent.MOUSE_WHEEL, onMouseWheel ); stage.addEventListener( KeyboardEvent.KEY_DOWN, onKeyDown ); //배치 arrange(); var textField:TextField = new TextField(); textField.textColor = 0xffffff; textField.autoSize = TextFieldAutoSize.LEFT; textField.text = "up key : increase fractures \ndown key : decrease fracture\nmouse drag : rotation"; addChild( textField ); } /** * 중앙에 배치 */ private function arrange( event:Event = null ):void { viewport.x = stage.stageWidth/2; viewport.y = stage.stageHeight/2; } /** * 렌더링 */ private function render( event:Event = null ):void { if( rotating ) { var dx:Number = mouseX - prevX; var dy:Number = mouseY - prevY; zRotation += dx; xRotation -= dy; //if( xRotation > 90 ) xRotation = 90; //if( xRotation < -90 ) xRotation = -90; prevX = mouseX; prevY = mouseY; rendering = true; } if( fracturesChanged ) { fracturesChanged = false; mesh = createGeodesicSphereMesh( radius, fractures ); projected = new Vector.<Number>(0,false); rendering = true; } if( rendering ) { rendering = false; world.identity(); world.appendRotation( zRotation, Vector3D.Z_AXIS ); world.appendRotation( xRotation, Vector3D.X_AXIS ); world.appendTranslation( 0, 0, zPosition ); Utils3D.projectVectors( world, mesh.vertices, projected, mesh.uvtData ); viewport.graphics.clear(); viewport.graphics.lineStyle( 1, 0xff0000, 0.5 ); //텍스쳐를 가지고 있다면 아래 주석을 풀고 Test 한다. //viewport.graphics.beginBitmapFill( texture, null, false, true ); viewport.graphics.drawTriangles( projected, mesh.indices, mesh.uvtData, mesh.culling ); //viewport.graphics.drawTriangles( projected, getSortedIndices(mesh), mesh.uvtData, mesh.culling ); viewport.graphics.endFill(); } } /** * 마우스 휠 처리 */ private function onMouseWheel( event:MouseEvent ):void { zPosition += event.delta * 50; } /** * 마우스 이벤트 - 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 ); rotating = true; prevX = mouseX; prevY = mouseY; break; case MouseEvent.MOUSE_UP: stage.removeEventListener( MouseEvent.MOUSE_MOVE, onMouseEvent ); stage.removeEventListener( MouseEvent.MOUSE_UP, onMouseEvent ); rotating = false; break; } } private function onKeyDown( event:KeyboardEvent ):void { switch( event.keyCode ) { case Keyboard.UP: fractures++; if( fractures > 25 ) fractures = 25; fracturesChanged=true; break; case Keyboard.DOWN: fractures--; if( fractures < 0 ) fractures = 0; fracturesChanged=true; break; } } } } import flash.display.*; /** * Geodesic Sphere Mesh 데이타 만듬 * @param radius 반경 * @param fractures 나눌 수 */ function createGeodesicSphereMesh( radius:Number, fractures:Number ):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.NEGATIVE ); var D2R:Number = Math.PI / 180; //Degree->Radian var TPI:Number = Math.PI * 2; var PI:Number = Math.PI; var HPI:Number = Math.PI / 2; var hnDE:int = fractures + 1; //적위 방향 쪼갠수/2 var nDE:int = 2 * hnDE; //적위 방향 쪼갠수 var nRA:int; //적위에 대한 적경 방향 쪼갠수 var ra:Number; //적경 (단위:라디안) var dec:Number; //적도(단위:라디안) var dDE:Number = 180/nDE*D2R; //적위 간격(단위:라디안) var dRA:Number; //적도 간격(단위:라디안) var i:int; var j:int; var x:Number; var y:Number; var z:Number; var sinDE:Number; var cosDE:Number; var sinRA:Number; var cosRA:Number; var u:Number; var v:Number; //////////////////////////////// // Vertex, UVT 데이타 만들기 //////////////////////////////// // 적위 -90->0 : vertices.push( 0, 0, -radius ); uvtData.push( 0, 0, 1 ); for( i = 0; i < hnDE; i++ ) { nRA = 4*(i+1); dRA = 360 / nRA * D2R; dec = -HPI+(i+1)*dDE; v = (HPI+dec)/PI; sinDE = Math.sin( dec ); cosDE = Math.cos( dec ); z = radius * sinDE; for( j = 0; j <= nRA; j++ ) { ra = j * dRA; sinRA = Math.sin( ra ); cosRA = Math.cos( ra ); x = radius * cosDE * cosRA; y = radius * cosDE * sinRA; u = ra / TPI; vertices.push( x, y, z ); uvtData.push( u, v, 1 ); } } // 적위 0 -> 90 for( i = 1; i < hnDE; i++ ) { nRA = 4*(hnDE-i); dRA = 360 / nRA * D2R; dec = dDE * i; v = (HPI+dec)/PI; sinDE = Math.sin( dec ); cosDE = Math.cos( dec ); z = radius * sinDE; for( j = 0; j <= nRA; j++ ) { ra = j * dRA; sinRA = Math.sin( ra ); cosRA = Math.cos( ra ); x = radius * cosDE * cosRA; y = radius * cosDE * sinRA; u = ra / TPI; vertices.push( x, y, z ); uvtData.push( u, v, 1 ); } } vertices.push( 0, 0, radius ); uvtData.push( 0, 1, 1 ); //////////////////////////////// // Vetex에 대한 index 만들기 // 폴리곤을 생성하기 위한 기초작업이다. //////////////////////////////// var k:int; var pt0:int, pt1:int, pt2:int; //하나의 폴리곤을 생성하는 Vertex의 index값 var u_idx_start:int, u_idx_end:int, u_idx:int; //상단 index var l_idx_start:int, l_idx_end:int, l_idx:int; //하단 index var isUp:int; //지그재그로 컬링 폴리곤을 생성하기 위해 var tris:int; //한개 분면에서 해당 적위에 대한 면의 수 var triIdx:int; //한개 분면에서 해당 적위에 대한 면의 수만큼 index를 증가하기 위해 사용 //적위 -90->0 tris = 1; u_idx_start = 0; u_idx_end = 0; for( i = 0; i < hnDE; ++i ) { //적위 간격으로 상하 시작index와 끝 index를 지정 l_idx_start = u_idx_start; l_idx_end = u_idx_end; u_idx_start += 4*i + 1; u_idx_end += 4*(i+1)+1; l_idx = l_idx_start; u_idx = u_idx_start; //4분면을 따라 Face를 만들도록 한다. for( k = 0; k < 4; ++k ) { isUp = 1; //한개 분면에 대한 Face의 index를 만들어준다. for( triIdx = 0; triIdx < tris; ++triIdx ) { if( isUp === 1) { pt0 = l_idx; pt2 = u_idx; u_idx++; pt1 = u_idx; isUp = 0; } else { pt0 = u_idx; pt1 = l_idx; l_idx++; pt2 = l_idx; isUp = 1; } indices.push( pt0, pt1, pt2 ); } } tris += 2; //한개의 분면에서 해당 적위에 대한 면의 수는 2씩 증가한다. } //적위 0 -> 90 for( i = hnDE-1; i >= 0; i-- ){ l_idx_start = u_idx_start; l_idx_end = u_idx_end; u_idx_start = u_idx_start + 4*(i+1)+1; u_idx_end = u_idx_end + 4*i + 1; tris -= 2; u_idx = u_idx_start; l_idx = l_idx_start; for( k = 0; k < 4; ++k ) { isUp = 0; for( triIdx = 0; triIdx < tris; triIdx++ ) { if( isUp === 1) { pt0 = l_idx; pt2 = u_idx; u_idx++; pt1 = u_idx; isUp = 0; } else { pt0 = u_idx; pt1 = l_idx; l_idx++; pt2 = l_idx; isUp = 1; } indices.push( pt0, pt1, pt2 ); } } } return mesh; } /** * 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; } Code Fullscreen Preview Fullscreen