Saturn 3D, 토성, 고리 중첩 해결한 것
토성 예제.
@author Yongho, Ji
@since 2009.07.07
@see http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WSF24A5A75-38D6-4a44-BDC6-927A2B123E90.html
@see http://blog.jidolstar.com/547
/**
* Copyright jidolstar ( http://wonderfl.net/user/jidolstar )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/kpQ2
*/
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.GraphicsTrianglePath;
import flash.display.Shape;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.display.TriangleCulling;
import flash.events.Event;
import flash.events.KeyboardEvent;
import flash.events.MouseEvent;
import flash.geom.Matrix3D;
import flash.geom.PerspectiveProjection;
import flash.geom.Rectangle;
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.07
* @see http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WSF24A5A75-38D6-4a44-BDC6-927A2B123E90.html
* @see http://blog.jidolstar.com/547
*/
public class Saturn3D extends Sprite
{
// 투영된 Vertex 정보
private var projected: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();
// 텍스쳐 정보 : 토성표면, 고리를 여기에 모두 합침
private var textureInfo:TextureInfo;
// Mesh 데이타 : 토성표면, 고리 포함
private var mesh:GraphicsTrianglePath;
// Viewport의 Z축 위치
private var viewPortZAxis:Number = 200;
// triangle을 보여줄지 여부
private var visibleTriangle:Boolean = false;
/**
* 생성자
*/
public function Saturn3D()
{
super();
//화면 설정
//stage.align = StageAlign.TOP_LEFT;
//stage.scaleMode = StageScaleMode.NO_SCALE;
//stage.quality = StageQuality.BEST;
//Texture 만들기
var faceTexture:BitmapData = new BitmapData( 400, 200, false );
var ringTexture:BitmapData = new BitmapData( 60, 30, false );
faceTexture.perlinNoise(64, 64, 3, 0, true, true, 7, true);
ringTexture.perlinNoise(64, 64, 3, 0, true, true, 7, true);
textureInfo = createTexture( faceTexture, ringTexture );
faceTexture.dispose();
ringTexture.dispose();
//Mesh 데이타
mesh = createMesh( textureInfo );
//viewport를 화면의 중심으로
viewport.x = stage.stageWidth/2;
viewport.y = stage.stageHeight/2;
addChild(viewport);
//projection의 fieldOfView를 60으로 지정
projection.fieldOfView = 60;
//이벤트 처리
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, mesh.vertices, projected, mesh.uvtData );
viewport.graphics.clear();
// Triangle 라인을 그림
if( visibleTriangle )
{
viewport.graphics.lineStyle( 1, 0xff0000, 1 );
}
//Texture 입힌다.
viewport.graphics.beginBitmapFill( textureInfo.texture, null, false, true );
viewport.graphics.drawTriangles( projected, getSortedIndices(mesh), mesh.uvtData, mesh.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;
import flash.display.BitmapData;
import flash.utils.ByteArray;
import flash.geom.Rectangle;
/**
* 토성 모양의 텍스쳐 정보를 가지는 클래스
*/
class TextureInfo
{
public var texture:BitmapData;
public var faceRect:Rectangle;
public var ringRect:Rectangle;
/**
* 생성자
* @param texture 텍스쳐가 담긴 BitmapData
* @param faceRect 텍스쳐에서 표면 영역
* @param ringRect 텍스쳐에서 고리 영역
*/
public function TextureInfo( texture:BitmapData, faceRect:Rectangle, ringRect:Rectangle )
{
this.texture = texture;
this.faceRect = faceRect;
this.ringRect = ringRect;
}
}
/**
* 텍스쳐 생성
* @param faceTexture 표면의 BitmapData
* @param ringTexture 고리의 BitmapData
* @return TexturInfo의 객체
*/
function createTexture( faceTexture:BitmapData, ringTexture:BitmapData ):TextureInfo
{
var texture:BitmapData;
texture = new BitmapData(
Math.max( faceTexture.width, ringTexture.width ),
faceTexture.height + ringTexture.height,
true,
0xff000000
);
faceTexture.lock();
ringTexture.lock();
var facePixels:ByteArray ;
var ringPixels:ByteArray;
facePixels = faceTexture.getPixels( new Rectangle( 0, 0, faceTexture.width, faceTexture.height ) );
ringPixels = ringTexture.getPixels( new Rectangle( 0, 0, ringTexture.width, ringTexture.height ) );
facePixels.position = 0;
ringPixels.position = 0;
texture.setPixels(
new Rectangle( 0, 0, faceTexture.width, faceTexture.height ),
facePixels
);
texture.setPixels(
new Rectangle( 0, faceTexture.height, ringTexture.width, ringTexture.height ),
ringPixels
);
var faceRect:Rectangle = new Rectangle( 0, 0, faceTexture.width, faceTexture.height );
var ringRect:Rectangle = new Rectangle( 0, faceTexture.height+1, ringTexture.width, ringTexture.height );
faceTexture.unlock();
ringTexture.unlock();
return new TextureInfo( texture, faceRect, ringRect );
}
/**
* 토성모양의 Mesh 데이타
* @param textureInfo 텍스쳐 정보
* @return Mesh 데이타
*/
function createMesh( textureInfo:TextureInfo ):GraphicsTrianglePath
{
var width:Number = textureInfo.texture.width;
var height:Number =textureInfo.texture.height;
var faceRect:Rectangle = textureInfo.faceRect;
var ringRect:Rectangle = textureInfo.ringRect;
var minU:Number;
var maxU:Number;
var minV:Number;
var maxV:Number;
//표면 Mesh 데이타 만들기
minU = faceRect.x / width;
maxU = (faceRect.x + faceRect.width) / width;
minV = faceRect.y / height;
maxV = (faceRect.y + faceRect.height) / height;
var faceMesh:GraphicsTrianglePath = createSphereMesh( 50, 24, 24, minU, maxU, minV, maxV );
//고리 Mesh 데이타 만들기
minU = ringRect.x / width;
maxU = (ringRect.x + ringRect.width) / width;
minV = ringRect.y / height;
maxV = (ringRect.y + ringRect.height) / height;
var ringMesh:GraphicsTrianglePath = createRingMesh( 70, 20, 50, minU, maxU, minV, maxV );
//고리 Mesh 데이타에서 Index 부분 조정
var deltaIndex:uint = faceMesh.vertices.length/3; //Vertex는 x,y,z 3개씩 묶이므로... ^^
var length:uint = ringMesh.indices.length;
for( var i:int = 0; i < length; i++ )
{
//아래와 같이 2개의 mesh가 합쳐지면 뒤에 붙는 ring부분의 index값이 변경되야한다.
ringMesh.indices[i] += deltaIndex;
}
//최종 Mesh 데이타 완성
var mesh:GraphicsTrianglePath = new GraphicsTrianglePath();
mesh.vertices = faceMesh.vertices.concat( ringMesh.vertices );
mesh.uvtData = faceMesh.uvtData.concat( ringMesh.uvtData );
mesh.indices = faceMesh.indices.concat( ringMesh.indices );
mesh.culling = TriangleCulling.NONE;
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;
}
/**
* 구 Mesh 데이터 작성
* @param radius 구의 반지름
* @param hDiv 수평 방향의 조각 수
* @param vDiv 높이 방향의 조각수
* @return mesh 데이터
*/
function createSphereMesh( radius:Number, hDiv:uint, vDiv:uint, minU:Number = 0, maxU:Number = 1, minV:Number = 0, maxV:Number = 1 ):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(
minU + (maxU-minU) * (i / hDiv),
minV + (maxV-minV) * (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, minU:Number = 0, maxU:Number = 1, minV:Number = 0, maxV:Number = 1 ):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(
minU,maxV,1,
maxU,maxV,1,
minU,minV,1,
maxU,minV,1
);
//Indices
n = i * 4;
mesh.indices.push( n, n+1, n+2, n+2, n+1, n+3 );
}
return mesh;
}