Geodesic Sphere using drawTriangle
Geodesic Sphere using drawTriangle()
@author Yongho Ji (jidolstar@gmail.com)
@since 2009.09.16
/**
* Copyright jidolstar ( http://wonderfl.net/user/jidolstar )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/gw52
*/
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;
}