Squiggly는 Flash Player 10, AIR 1.5이상에서 동작한다. 이 엔진은 ActionScript 3.0 기반이며 영어로 문장에서 사용된 단어를 영어사전의 그것과 비교하여 단어의 철자를 체크한다. 이번 업데이트는 TLF(Adobe Text Layout Framework)를 기반으로 하는 Flex 4 Spark 컴포넌트를 지원하도록 했다. Squiggly는 Flex Builder, Flash Builder, Flash CS4등 다양한 툴에서 사용할 수 있다. 단지 아쉬운 것은 영어단어만 체크한다는 점이다.
3D 환경맵핑(Environment Mapping)의 한 기법으로 Cube Maps가 있다. 말그대로 직육면체의 각면에 대한 텍스쳐 이미지로 3D 환경을 조성하는 방법인데 아래 이미지를 보면 바로 이해할 수 있다.
출처 : developer.com
Cube Maps는 환경 맵핑 방법중 가장 빠르고 매우 사실적으로 묘사할 수 있는 방법으로 내가 알기로는 게임등에 자주 사용하는 것으로 알고 있다. 단점은 모서리 부분에서 약간 티가 난다는 점이다.
Flash Player 10부터 3D API를 제공한다. 이 API들을 적절히 활용하면 Papervision3D나 Away3D와 같은 라이브러리를 이용하지 않고도 어려움 없이 Cube Maps을 구현할 수 있다.
아래 프로그램은 Flash Player 10에서 제공하는 3D API만 이용해서 Cube Map을 구현한 것이다. 마우스 드래그로 회전이 가능하다.
마우스로 Drag하면 회전이 됩니다. (Mac에서 화면이 작게 나오는데 아직 이유를 모르겠네요 ^^;)
위와 같은 프로그램을 만들기 위해 6면을 가지는 텍스쳐 이미지가 필요하다. 인터넷에 많이 있는데 아래처럼 Layout이 여러종류가 있다.
Cube Map Layout 출처 : cgtextures.com
나는 가장 일반적이고 자주 쓰이는 Horizontal Cross방식 대신에 Horizontal Strip(NVidia DDS Exporter Layout)을 이용했다. 이 방식은 아래같이 구성된다.
Cube Map Layout 출처 : cgtextures.com
이러한 이미지가 어떻게 Cube Map을 만들 수 있는지 아래 화면을 보면 쉽게 알 수 있다.
마우스로 Drag하면 회전이 됩니다.
아래는 위 프로그램에 대한 소스이다.
package
{
import flash.display.*;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.geom.*;
import flash.utils.getTimer;
[SWF(frameRate=60, backgroundColor=0x000000)]
/**
* Cube Map 예제
* @author Yongho, Ji (http://blog.jidolstar.com/574)
* @since 2009.08.05
*/
public class CubeSky extends Sprite
{
[Embed(source="sky.jpg")]
private const SKY:Class;
//투영된 Vectex 정보
private var projected:Vector.<Number>;
//World 변환 행렬
private var world:Matrix3D;
//투영을 위한 변환행렬
private var projection:Matrix3D;
//Mesh 데이터
private var mesh:GraphicsTrianglePath
//Texture
private var texture:BitmapData = (new SKY as Bitmap).bitmapData;
//Viewport (3D 렌더링 대상)
private var viewport:Shape;
//Viewport의 Z축 위치
private var viewPortZAxis:Number = 0;
public function CubeSky()
{
//Viewport
viewport = new Shape;
addChild( viewport );
//투영 변환 행렬
var p:PerspectiveProjection = new PerspectiveProjection()
p.fieldOfView = 30;
projection = p.toMatrix3D();
//World 변환 행렬
world = new Matrix3D();
var check3D:Vector3D = Utils3D.projectVector(
projection,
new Vector3D(1.0,0,1.0)
);
//Mesh 데이타
mesh = createCubeMesh( check3D.x );
projected = new Vector.<Number>(0,false);
viewPortZAxis = check3D.x;// * 4;
//이벤트 핸들러 등록
stage.addEventListener( Event.RESIZE, resize );
stage.addEventListener( Event.ENTER_FRAME, render );
//stage.addEventListener( MouseEvent.MOUSE_WHEEL, onMouseWheel );
stage.addEventListener( MouseEvent.MOUSE_DOWN, onMouseEvent );
resize();
}
private function resize( event:Event = null ):void
{
viewport.x = stage.stageWidth/2;
viewport.y = stage.stageHeight/2;
}
private function render( event:Event = null ):void
{
world.identity(); //단위행렬로 전환
world.appendRotation( zRotation, Vector3D.Z_AXIS );
world.appendRotation( 90+xRotation, Vector3D.X_AXIS );
world.appendTranslation(0, 0, viewPortZAxis); //이동
world.append(projection); //투영 변환 적용
// mesh 데이터를 투영하여 projected 생성
// uvtData도 갱신된다. 갱신되는 데이터는 T값이다.
Utils3D.projectVectors( world, mesh.vertices, projected, mesh.uvtData );
// texture를 이용해 렌더링
viewport.graphics.clear();
//viewport.graphics.lineStyle( 1, 0xff0000 );
viewport.graphics.beginBitmapFill( texture, null, false, true );
viewport.graphics.drawTriangles( projected, mesh.indices, mesh.uvtData, mesh.culling );
//Cube는 z축을 굳이 정렬할 필요 없다.
//viewport.graphics.drawTriangles( projected, getSortedIndices(mesh), mesh.uvtData, mesh.culling );
}
/**
* 마우스 휠 처리
*/
private function onMouseWheel( event:MouseEvent ):void
{
viewPortZAxis += event.delta * 10;
}
private var xRotation:Number = 0;
private var zRotation:Number = 0;
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;
zRotation += dx;
xRotation += dy;
if( xRotation > 90 ) xRotation = 90;
if( xRotation < -90 ) xRotation = -90;
prevX = mouseX;
prevY = mouseY;
break;
case MouseEvent.MOUSE_UP:
stage.removeEventListener( MouseEvent.MOUSE_MOVE, onMouseEvent );
stage.removeEventListener( MouseEvent.MOUSE_UP, onMouseEvent );
break;
}
}
}
}
import flash.display.GraphicsTrianglePath;
import flash.display.TriangleCulling;
/**
* Cube Mesh 데이타 작성
* @param size 한변의 사이즈
* @return mesh 데이터
*/
function createCubeMesh( size:Number = 512 ):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 );
var s:Number = size/2;
var w:Number = size * 6;
var u00:Number = 0;
var u01:Number = (size-1)/w;
var u10:Number = size/w;
var u11:Number = (size*2-1)/w;
var u20:Number = (size*2)/w;
var u21:Number = (size*3-1)/w;
var u30:Number = (size*3)/w;
var u31:Number = (size*4-1)/w;
var u40:Number = (size*4)/w;
var u41:Number = (size*5-1)/w;
var u50:Number = (size*5)/w;
var u51:Number = (size*6-1)/w;
mesh.vertices.push(
//Right
s,-s,-s, //0
s,-s,s, //1
s,s,s, //2
s,s,-s, //3
//Left
-s,s,-s, //4
-s,s,s, //5
-s,-s,s, //6
-s,-s,-s, //7
//Top
-s,-s,s, //8
-s,s,s, //9
s,s,s, //10
s,-s,s, //11
//Bottom
-s,s,-s, //12
-s,-s,-s, //13
s,-s,-s, //14
s,s,-s, //15
//Front
-s,-s,-s, //16
-s,-s,s, //17
s,-s,s, //18
s,-s,-s, //19
//Back
s,s,-s, //20
s,s,s, //21
-s,s,s, //22
-s,s,-s //23
);
mesh.indices.push(
0,1,2, 0,2,3, //Right
4,5,6, 4,6,7, //Left
8,9,10, 8,10,11, //Top
12,13,14, 12,14,15, //Bottom
16,17,18, 16,18,19, //Front
20,21,22, 20,22,23 //Back
);
mesh.uvtData.push(
//Right
u00, 1, 1,
u00, 0, 1,
u01, 0, 1,
u01, 1, 1,
//Left
u10, 1, 1,
u10, 0, 1,
u11, 0, 1,
u11, 1, 1,
//Top
u20, 1, 1,
u20, 0, 1,
u21, 0, 1,
u21, 1, 1,
//Bottom
u30, 1, 1,
u30, 0, 1,
u31, 0, 1,
u31, 1, 1,
//Front
u40, 1, 1,
u40, 0, 1,
u41, 0, 1,
u41, 1, 1,
//Back
u50, 1, 1,
u50, 0, 1,
u51, 0, 1,
u51, 1, 1
);
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;
}
아래 이미지는 위 프로그램에서 사용한 이미지이다.
위 프로그램에서는 Graphics.drawTriangle() 메소드를 이용했다. 하지만 이 함수를 사용하지 않고도 만들수 있다. 왜냐하면 Flash Player 10부터는 DisplayObject에 대해 x,y,z축 회전 및 이동 API가 추가되었기 때문이다. 그러면 위 프로그램 소스처럼 힘들게 vertex, index, uvt 데이타를 만들지 않아도 될 것이다.
내 생각에 Flash에서 3D 개발을 위해 Graphics.drawTriangle()와 DisplayObject의 3D API, Matrix3D 등을 서로 섞어가면서 만드는 것이 좋을 것 같다.
Papervision3D나 Away3D 라이브러리를 이용하지 않고 Flash Player 10 3D API만을 이용해 태양계의 행성인 토성을 그려보았다. 토성을 그리기 위해 먼저 표면부분과 고리부분의 Texture를 구해야한다. 아래 사이트에서 다양한 행성의 Texture 그림을 구할 수 있다.
토성을 모양을 만들기 위해 토성의 표면과 고리에 대한 Mesh데이타를 만들어야 한다. 아래코드는 고리(Ring) 부분의 Mesh데이타를 만드는 부분이다. 표면부분과 다르게 고리 Texture를 반복해서 그리는 데이타를 만든다.
/**
* 고리 Mesh 데이터 작성
* @param radius 고리 안쪽 반지름
* @param thickness 고리 두께
* @param div 고리 조각수
* @return mesh 데이터
*/
function createRingMesh( radius:Number, thickness:Number, div:uint ):GraphicsTrianglePath
{
var vertices:Vector. = new Vector.( 0, false );
var indices:Vector. = new Vector.( 0, false );
var uvtData:Vector. = new Vector.( 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;
}
Enterfame시에 렌더링을 실시하는데 Utils3D.projectVectors(), graphics.beginBitmapFill(), graphics.drawTriangles() 메소드를 2번씩 호출한다. 즉, 토성의 구와 고리를 그리기 위해 투영, 렌더링을 2번씩 하는 것이다.
언뜻보면 이 코드는 문제가 없어보이지만 실제로 실행해보면 바로 문제가 있다는 것을 확인할 수 있다.
아래는 이 프로그램을 실행한 것이다.
마우스로 돌려보고 화살표키로 확대/축소도 할 수 있다. 또한 Space키 누르면 삼각형 부분이 보인다.
실행한 결과를 보면 문제가 있다. 토성 표면 뒤에 고리가 보인다. 당연히 저렇게 그려지면 안된다. 이 문제는 위의 방법처럼 Texture와 Mesh데이타가 2개여서는 방법이 나오지 않는다. 왜냐하면 graphics.drawTriangles() 함수를 2번 그려주게 되면 먼저 그린 것은 항상 나중에 그린것에 의해 가려지기 때문이다.
위 함수처럼 1번만 투영/렌더링 처리하면 이제 토성의 표면과 고리는 아래처럼 자연스럽게 나오게 된다.
하지만 완벽하지 않다. 이유는 렌더링시 Culling을 None으로 지정했기 때문이다. 이는 보이지 않는 표면까지 그린다는 의미이다. 고리의 경우 양쪽면이 다보여야한다. 그러므로 고리의 Mesh데이타는 None으로 지정한다. 하지만 토성 표면의 경우는 화면 앞쪽을 향하는 부분만 보이면 되므로 Culling을 Positive로 지정하면 된다. 첫번째 예제에서는 어짜피 Mesh 데이터가 2개이므로 상관없었지만 두번째의 경우에는 양면 다보여야하는 고리때문에 토성의 표면도 None처리 했다. 이는 보이지 않는 부분까지 렌더링하므로 그만큼 속도저하가 일어난다. 이를 해결하기 위해 Positive로 지정하되 고리를 앞뒤면으로 겹쳐서 그리면 된다. 하지만 이 방법밖에 없는지 의문이다.
정리하기
이런 작업은 신나는 일이다. ^^
이 토성에 Light효과를 주어 Shading 예제도 만들어봐야 겠다.
참고로 모든 작업은 Flash Builder 4에서 했으며 Flex 4 SDK를 이용했다. 결과물은 Flash Player 10이상 설치된 브라우져에서만 확인할 수 있다.
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.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.utils.getTimer;
[SWF(frameRate=60, backgroundColor=0x000000)]
/**
* 3D Texture 예제.
* @author Yongho, Ji
* @since 2009.07.01
* @see http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WSF24A5A75-38D6-4a44-BDC6-927A2B123E90.html
*/
public class Texture3D extends Sprite
{
[Embed(source="mybaby.png")]
private var ImageClass:Class;
// 투영된 Vertex 정보
private var projected:Vector.<Number>;
// 투영
private var projection:PerspectiveProjection = new PerspectiveProjection();
// World 변환 행렬
private var world:Matrix3D = new Matrix3D();
// Viewport (3D 렌더링 대상)
private var viewport:Shape = new Shape();
// Mesh 데이터
private var mesh:GraphicsTrianglePath
// Texture
private var texture:BitmapData = (new ImageClass() as Bitmap).bitmapData;
//triangle을 보여줄지 여부
private var visibleTriangle:Boolean = false;
//Texture를 보여줄지 여부
private var visibleTexture:Boolean = true;
//실린더 Mesh번호
private const MESH_CYLINDER:int = 1;
//원형체 Mesh번호
private const MESH_TORUS:int = 2;
//선택한 Mesh 번호
private var selectedMesh:int;
//Viewport의 Z축 위치
private var viewPortZAxis:Number = 300;
/**
* 생성자
*/
public function Texture3D()
{
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;
//mesh 데이터 설정
selectMesh( MESH_CYLINDER );
//이벤트 처리
stage.addEventListener( Event.RESIZE, onResize );
addEventListener( Event.ENTER_FRAME, onEnterFrame );
stage.addEventListener( MouseEvent.CLICK, onMouseClick );
stage.addEventListener( KeyboardEvent.KEY_DOWN, onKey );
stage.addEventListener( MouseEvent.MOUSE_WHEEL, onMouseWheel );
}
/**
* Mesh를 선택한다.
*/
private function selectMesh( meshNumber:int ):void
{
if( selectedMesh == meshNumber ) return;
selectedMesh = meshNumber;
//투영결과 Vertex 데이타를 초기화시킨다.
//이것을 해야 Mesh데이터가 바뀔때마다 drawTriangle에서 에러 발생 안함
projected = new Vector.<Number>(0, false);
switch( selectedMesh )
{
//실린더
case MESH_CYLINDER:
mesh = createCylinderMesh( 50, 100, 20, 5 );
break;
//원형체
case MESH_TORUS:
mesh = createTorusMesh( 50, 25, 32, 16 );
break;
}
}
/**
* 사이즈 변경시 처리
*/
private function onResize( event:Event ):void
{
//viewport는 항상 화면의 중심에 위치하도록 처리
viewport.x = stage.stageWidth/2;
viewport.y = stage.stageHeight/2;
}
/**
* 프레임 마다 처리.
*/
private function onEnterFrame( event:Event ):void
{
world.identity(); //단위행렬로 전환
world.appendRotation( getTimer() * 0.027, Vector3D.X_AXIS ); //X축 회전
world.appendRotation( getTimer() * 0.061, Vector3D.Y_AXIS ); //Y축 회전
world.appendTranslation(0, 0, viewPortZAxis); //이동
world.append(projection.toMatrix3D()); //투영 변환 적용
// mesh 데이터를 투영하여 projected 생성
// uvtData도 갱신된다. 갱신되는 데이터는 T값이다.
Utils3D.projectVectors( world, mesh.vertices, projected, mesh.uvtData );
// texture를 이용해 렌더링
viewport.graphics.clear();
// Triangle 라인을 그림
if( visibleTriangle )
{
viewport.graphics.lineStyle( 1, 0xff0000, 1.0 );
}
//Texture 입힌다.
if( visibleTexture )
{
viewport.graphics.beginBitmapFill( texture, null, false, true );
viewport.graphics.drawTriangles( projected, getSortedIndices(mesh), mesh.uvtData, mesh.culling );
}
else
{
viewport.graphics.beginFill( 0x00ccff, 1.0 );
viewport.graphics.drawTriangles( projected, getSortedIndices(mesh), null, mesh.culling );
viewport.graphics.endFill();
}
}
/**
* 마우스 클릭 처리
*/
private function onMouseClick( event:MouseEvent ):void
{
//삼각형 선 Visible 바꿈
visibleTriangle = !visibleTriangle;
}
/**
* 키보드 이벤츠 처리
*/
private function onKey( event:KeyboardEvent ):void
{
if( event.charCode == 116 )
{
visibleTexture = !visibleTexture;
}
else
{
selectMesh( event.charCode - 48 ); //1,2...
}
}
/**
* 마우스 휠 처리
*/
private function onMouseWheel( event:MouseEvent ):void
{
viewPortZAxis += event.delta * 10;
}
}
}
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 height 원통의 높이
* @param hDiv 반지름 방향으로 조각 수
* @param vDiv 높이 방향의 조각수
* @return mesh 데이터
*/
function createCylinderMesh( radius:Number, height: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.NONE );
for( var i:uint = 0; i <= hDiv; i++ )
{
var theta:Number = Math.PI * 2 * i / hDiv; //z축에서 y축으로 잰각
for( var j:uint = 0; j <= vDiv; j++ )
{
var x:Number = -height/2 + j * height/vDiv;
var y:Number = radius * Math.sin( theta );
var z:Number = radius * Math.cos( theta );
mesh.vertices.push( x, y, z ); //하나의 Vertex 데이타
mesh.uvtData.push( i / hDiv, j / vDiv, 1 ); //하나의 UVT 데이타 . Texture의 점과 Vectex를 일치시켜 Texture를 입히기 위한 데이타이다.
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); //삼각형의 index이다. culling방향 고려
}
}
}
return mesh;
}
/**
* 원환체(도너츠모양) Mesh 데이터 작성
* @param hRadius 원환체의 수평축 반지름
* @param vRadius 원환체의 수직축 반지름
* @param hDiv 수평 방향의 조각 수
* @param vDiv 높이 방향의 조각수
* @return mesh 데이터
*/
function createTorusMesh( hRadius:Number, vRadius: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.NONE );
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 * 2 * j / vDiv;
var r:Number = Math.cos(s2) * vRadius + hRadius;
var x:Number = Math.cos(s1) * r;
var y:Number = Math.sin(s1) * r;
var z:Number = Math.sin(s2) * vRadius;
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(b, a + 1, a, a + 1, b, b + 1);
}
}
}
return mesh;
}
아래는 위 프로그램을 실행한 것이다. Flash Player 10이 브라우져에 설치되어야 제대로 볼 수 있겠다.
키 1, 2 - 형태바꾸기
키 T(영문) - Texture 입히기 여부
마우스 클릭 - 삼각형 보이기 여부
마우스 휠 - 확대/축소
위 프로그램에서 사용된 GraphicsTrianglePath는 2D 공간에 3D 기하 도형 렌더링을 지원하기 위해 사용되는 클래스이다. 특별한 기능은 없지만 3D 데이터를 다룰 때 index, vertex, uvt 등의 데이타를 모두 담을 수 있기 때문에 사용하면 유용하다.
위 프로그램에서 원뿔, 구에 대한 mesh 함수를 추가하면 더 좋은 예제가 될 것이라 생각한다. 또한 광원(light)처리까지 곁들이면 금상첨화일 것이다.
Flash Player 10부터 3D를 구현할 수 있는 일련의 Native API 코드가 추가되었다. Papervision3D나 Away3D 처럼 멋진 3D 효과를 만들기에는 부족하긴 하지만 여러가지 시도는 해볼 수 있겠다.
Adobe에서 제공하는 Flash용 ActionScript 3.0 프로그래밍 가이드를 살펴보면 3차원(3D)에서 작업에 대한 내용이 있다. 이 문서만 보아도 3D 표현을 하는데 큰 도움을 받을 수 있다. 문서에는 몇가지 예제가 있는데 예제중에 UV맵핑에 올라온 예제를 가지고 몇가지 테스트 코드를 만들었다.
테스트에 참가할 나의 딸, 예진이의 사진이다. ^^
T값을 이용한 원근표현
UV 맵핑은 텍스쳐(Texture) 맵핑이다. 텍스쳐는 일반적인 2D 사진을 의미하며 이 사진을 삼각형의 형태로 쪼개서 여러가지 3D 표현을 할 수 있다. 사진가지고 깃발처럼 펄럭거리게 할 수 있고, 구의 형태로 만들수도 있는 것이다.
UV맵핑은 사진의 어느부분을 버텍스(Vertex, 꼭지점)으로 삼을것인가 지정한다. UV맵핑에서 U는 사진의 가로축을 말하며 V는 사진의 세로축을 의미한다. 가로축/세로축 범위는 0~1까지다. 그러므로 U값이 0이면 사진의 좌측맨끝부분을 의미하며 1이면 사진의 우측맨끝을 말한다. V값도 마찬가지로 0이면 사진의 위끝부분, 1이면 맨아래 부분을 의미한다. UV값은 3D 버텍스와 1:1 대응되도록 한다. 그래서 지정된 1개의 버텍스는 UV좌표에 지정된 사진의 좌표에 대응되어 맵핑이 이뤄지도록 하는 것이다. 이러한 맵핑을 할 수 있도록 하는 함수가 Graphics의 drawTriangle()이다. 이 함수의 1번째 인자가 버텍스값이고 3번째 인자가 UV데이타 값이다.
사진의 경우 이미지 그대로 화면에 표현하기 위해 삼각형이 최소 2개가 필요하다. 사진에서 표현되는 실제 버텍스는 4개이지만 각각 삼각형의 버텍스는 6개가 된다. 이중에 2개의 버텍스는 중복이 된다. 이 중복을 제거하기 위해 사용하는 것이 인덱스이다. 인덱스를 이용하면 삼각형을 그리기 위한 버텍스의 순서를 정의함으로서 중복된 버텍스를 없앤다. 이 값은 drawTriangle()의 2번째 인자값으로 지정할 수 있다.
여기서 사용한 T값은 버텍스의 위치가 3D 형태로 변하면서 사진도 이에 맞게 크기를 조절할 필요가 있기 때문에 사용하는 값이다. 이를 이용해 원근표현을 할 수 있게 된 것이다.
아래 코드를 보면서 UV데이타와 T값을 이용한 3D 표현법에 대한 기초를 공부할 수 있다.
(소스를 복사할때는 이 소스를 직접복사하지 말고 소스 위에 마우스를 올려 소스보기 아이콘이 뜰때 그것을 눌러 새창이 뜨면 소스를 긁어다가 사용하면 되겠다.)
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display.TriangleCulling;
import flash.events.Event;
import flash.utils.getTimer;
/**
* 3D 회전. T값을 이용해 원근 적용
* @author Yongho, Ji
* @since 2009.06.30
* @see http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WS509D19BB-239B-4489-B965-844DDA611AE7.html
*/
public class Baby3D extends Sprite
{
[Embed(source="mybaby.png")]
private var ImageClass:Class;
//평면 버택스 좌표와 t값
private var x1:Number = -100, y1:Number = -100, z1:Number =0, t1:Number = 0;
private var x2:Number = 100, y2:Number = -100, z2:Number =0, t2:Number = 0;
private var x3:Number = 100, y3:Number = 100, z3:Number =0, t3:Number = 0;
private var x4:Number = -100, y4:Number = 100, z4:Number =0, t4:Number = 0;
//초점거리
private var focalLength:Number = 200;
//버텍스에 대한 인덱스
private var indices:Vector.<int>;
private var container:Sprite;
private var bitmapData:BitmapData;
public function Baby3D()
{
super();
this.stage.align = StageAlign.TOP_LEFT;
this.stage.scaleMode = StageScaleMode.NO_SCALE;
//1평면당 2개의 삼각형을 가진다.
indices = new Vector.<int>;
indices.push( 0,1,3, 2,3,1 );
container = new Sprite();
container.x = 200;
container.y = 200;
addChild( container );
var bitmap:Bitmap = new ImageClass();
bitmapData = bitmap.bitmapData;
this.addEventListener( Event.ENTER_FRAME, rotatePlane );
}
private function rotatePlane( event:Event = null ):void
{
//버텍스 회전
var ticker:Number = getTimer() / 400;
z2 = z3 = -(z1 = z4 = 100 * Math.sin( ticker ) );
x2 = x3 = -(x1 = x4 = 100 * Math.cos( ticker ) );
//t값 계산
t1 = focalLength / ( focalLength + z1 );
t2 = focalLength / ( focalLength + z2 );
t3 = focalLength / ( focalLength + z3 );
t4 = focalLength / ( focalLength + z4 );
//삼각형 버택스
//t값을 이용해서 버텍스 좌표에 원근값을 주도록 한다.
var vertices:Vector.<Number> = new Vector.<Number>;
vertices.push(
x1*t1,y1*t1,
x2*t2,y2*t2,
x3*t3,y3*t3,
x4*t4,y4*t4
);
// UV 맵핑
// T값을 이용해 원근값이 적용된 맵핑이미지를 만들기 위해
var uvtData:Vector.<Number> = new Vector.<Number>;
uvtData.push(
0,0,t1,
1,0,t2,
1,1,t3,
0,1,t4
);
//그리기
container.graphics.clear();
container.graphics.lineStyle( 1, 0xff0000, 1.0 );
container.graphics.beginBitmapFill( bitmapData );
container.graphics.drawTriangles( vertices, indices, uvtData, TriangleCulling.NONE );
container.graphics.endFill();
}
}
}
위 코드는 Adobe에서 제공하는 예제를 약간 변형한 것이다. 실행결과는 다음과 같다.
소개한 코드는 그냥 실행하는데 그치는 것은 좋지 못하다. 값을 적절하게 변경해가면서 어떻게 변경되는가 알아보는 것도 좋은 학습법이라 생각한다.
DisplayObject의 Y축을 이용한 회전. 원근투영 이용
위 예제와 UV맵핑은 동일하지만 T값은 1로 고정한다. 그리고 Y축만 회전하고 원근표현은 T값 대신에 perspectiveProjection 속성을 이용한다.
T값을 1로 한다는 것은 T값을 가지고 원근표현을 하지 않겠다는 의미이다. 아래 예제를 보면 알겠지만 T값이 변하지 않기 때문에 EnterFrame 이벤트 발생시마다 그려주는 대신 생성자에서 한번만 그렸다.
원근투영(Perspective Projection)은 Flash Player 10에서 기본적으로 지원해준다. 가장 단순한 투영방식으로 하나의 소실점을 가지도록 하는 Linear한 투영방식이다. 만약 이 투영방식을 사용하지 않는다면 Matrix3D를 이용해 다른 방식의 원근투영방식을 적용해야 한다.
아래 코드에서 root.transform.perspectiveProjection.fieldOfView는 100으로 설정되어 있다. 이 값은 T값으로 원근표현할때와 비슷한 느낌을 주기 위한 값이다. fieldOfView 0~180까지 적용할 수 있다. 0은 말그대로 매우 먼곳에서 대상을 바라보는 것과 같다. 즉, 왜곡이 없는 원통투영이 된다. 하지만 180에 가까워 질 수록 대상과 가까이서 보는 느낌을 주게 된다. 한개의 소실점을 가지는 원근투영이 된다. 이 값을 적절하게 조절하면서 실행해보자 무슨 의미인지 알 수 있을 것이다.
package
{
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageScaleMode;
import flash.display.TriangleCulling;
import flash.events.Event;
/**
* 3D 회전. 원근 투영을 이용
* @author Yongho, Ji
* @since 2009.06.30
* @see http://help.adobe.com/ko_KR/ActionScript/3.0_ProgrammingAS3/WS36223081-8938-4b45-BB89-F1F8B1A52E4E.html
*/
public class Baby3D_2 extends Sprite
{
[Embed(source="mybaby.png")]
private var ImageClass:Class;
private var container:Sprite;
public function Baby3D_2()
{
super();
this.stage.align = StageAlign.TOP_LEFT;
this.stage.scaleMode = StageScaleMode.NO_SCALE;
container = new Sprite();
container.x = 200;
container.y = 200;
addChild( container );
//원근투영의 FiendOfView 설정. 기본값은 55임
root.transform.perspectiveProjection.fieldOfView = 100;
//비트맵 데이타 생성 얻어오기
var bitmap:Bitmap = new ImageClass();
var bitmapData:BitmapData = bitmap.bitmapData;
//버텍스
var vertices:Vector.<Number> = new Vector.<Number>;
vertices.push(
-100,-100,
100,-100,
100,100,
-100,100
);
//위 베텍스에 대한 인덱스 , 1평면당 2개의 삼각형을 가지고 있다.
var indices:Vector.<int> = new Vector.<int>;
indices.push( 0,1,3, 2,3,1 );
//UV 맵핑 데이타
var uvtData:Vector.<Number> = new Vector.<Number>;
uvtData.push(
0,0,1,
1,0,1,
1,1,1,
0,1,1
);
//그리기
container.graphics.clear();
container.graphics.lineStyle( 1, 0xff0000, 1.0 );
container.graphics.beginBitmapFill( bitmapData );
container.graphics.drawTriangles( vertices, indices, uvtData, TriangleCulling.NONE );
container.graphics.endFill();
this.addEventListener( Event.ENTER_FRAME, rotatePlane );
}
private function rotatePlane( event:Event = null ):void
{
//컨테이너 회전
container.rotationY -= 5;
}
}
}
실행된 이미지가 조금 다르게 보일 것이다. T값을 이용한 원근표현 방식의 경우 렌더링을 계속하기 때문에 삼각형 선(빨간색)이 왜곡되지 않는다. 하지만 원근투영방식을 이용하면 한번 렌더링을 해주고 그것 자체를 회전시키는 방식이기 때문에 삼각형 선이 왜곡되어 보인다.
위 예제들은 쉬운 코드들이긴 하지만 UV맵핑, T값, 원근투영, 버텍스 등에 대한 이해가 없다면 어려울 수 밖에 없다. 3D 프로그래밍을 한번도 안한 분이라면 다른 서적이나 인터넷을 통해 학습자료를 찾아 공부하면 좋겠다는 생각이 든다.
Flash Player 10의 3D 기법은 아주 기초 수준이기 때문에 Papervision3D, Away3D처럼 화려한 3D를 만드는 것은 기대하기 어렵다. 하지만 Native수준에서 기능을 제공하므로 빠른 연산이 가능해서 간단한 3D 구현하겠다면 위 예제 처럼 만들자. 앞으로 Papervision3D, Away3D도 Flash Player 10 기반으로 제공한다고 하니깐 조금더 향상된 퍼포먼스를 가진 Flash 3D 애플리케이션을 기대해도 좋을 것 같다.
Adobe Flash Builder 4 Beta가 배포되었습니다. 공식판은 아니고요. Flash Builder 4는 Flex Builder 3의 차기버전으로 Flex SDK 4(Flex Gumbo)를 기본 SDK로 설정되어 있습니다. Flex SDK 4로 만들어진 애플리케이션은 기본적으로 Flash Player 10 버전에서 동작이 가능합니다. 물론 기존 Flex SDK 3도 사용할 수 있습니다.
아래 링크에서 언제든지 다운로드 받을 수 있습니다.(회원가입하셔야 합니다.) 윈도우, 맥 버전이 있으니 자신의 운영체제에 맞게 다운로드 받으시면 됩니다.
C:/Program Files/Adobe/Flash Builder Beta 경로에 기본 설치되는군요.
설치중인 화면입니다.
이름도 Gumbo입니다. ㅎㅎ 아직 Beta라는 것이겠죠.
실행해 보겠습니다.
30일 Trial버전으로 사용해봅니다.
첫 실행 화면입니다. FB 문자가 선명하네요. FB는 Flex Builder가 아니라 Flash Builder 입니다.
2. 사용해보기
메뉴 구성 변화
FXP 임포트도 되고 Test Case Class, Test Suit Class도 만들 수 있도록 되어 있네요. Flex Builder로 테스트 주도형 제작이 가능해지겠군요.
Data 메뉴가 상당히 직관적으로 바뀌었네요. 또한 더욱 많은 서비스도 지원하도록 만들어졌고요. 진정한 RIA를 구축하기 위한 서버측 기술 지원을 배려한 기능이군요.
이전에 없었던 Data/Services, ASDoc, Network Monitor 등이 지원되네요.
아래는 네트워크 모니터툴입니다. RPC 통신등을 할 때 서버와 통신이 어떻게 되는지 확인하는데 요긴하게 사용할 수 있을 것 같습니다. 이제 Http Watcher가 필요 없겠군요!!!!!
Flex 코딩해보기
Flex Project를 만들어보겠습니다. SDK를 Flex SDK 3.4와 Flex 4.0을 사용할 수 있도록 되어 있네요. Flex 4.0 기반에서 작업해 보겠습니다.
FDT(Flash Development Tools)에서 보이는 구조와 비슷하네요. 프로젝트에 사용하고 있는 Flex 4.0 SDK 라이브러리들도 보이고 각각의 (+)버튼을 누르면 정의된 클래스도 한눈에 볼 수 있습니다. 이거 정말 편해졌군요. SWC도 못봤던 netmone.swc, sparksins.swc등이 있습니다.
재미있는 것은 Flex Gumbo가 Fx이니셜로 컴포넌트가 만들어진 것으로 알고 있었는데 FxApplication이 아닌 그냥 Application입니다.
그리고 몇가지 namespace(fx, s, mx)로 구분 되었네요. ComboBox가 DropDownList로 이름이 바뀐 것 같습니다.
Adobe Stratus는 Flash Player 10이상 또는 AIR 1.5 이상에서 RTMFP(Real Time Media Flow Protocol)이라 불리우는 통신프로토콜을 이용하여 클라이언트끼리 Peer to Peer(P2P)가 가능하게한 기술이다. TCP가 아닌 UTP를 기반으로 하기 때문에 통신 속도에는 좋지만 데이타 전송에 대한 신뢰도는 떨어지지 않을까 판단한다. 이러한 이유로 Stratus는 채팅, 각종 미디어 통신, 멀티 게임등을 만드는데 적합하다고 할 수 있다.
위 그림에서 볼 수 있듯이 기존 RTMP는 사용자가 서버를 통해서만 다른 사용자에게 접근이 가능했다. 하지만 Adobe Stratus에서 제공하는 RTMFP를 이용하면 처음 Staratus 서버와 접속하여 인증절차만 완료하면 Flash Player 끼리 P2P통신이 가능해진다. 이렇게 함으로서 서버의 부하를 줄일 수 있게 되었다.
필자는 Adobe에 올라온 예제를 가지고 테스트를 해봤다. 이 예제는 Adobe Stratus를 가지고 어떤 기능까지 가능한가 총체적으로 보여주는 예제이다. 단순한 채팅뿐 아니라 마이크, 스피커, 비디오 예제가 한 소스에 들어가 있다. 예제는 [여기]에서 실행해볼 수 있고 또한 다운로드 받을 수 있다.
필자는 제공된 Stratus 예제 Flex 애플리케이션을 수정하지 않고 서버측 사용자 등록하는 코드인 reg.cgi를 PHP로만 포팅해서 테스트 해봤다.
Stratus는 현재 Adobe측에 서버를 두고 있고 공개하고 있진 않다. 그러므로 자신의 서버에 Stratus를 설치하지는 못한다. 하지만 개발키를 부여하고 있기 때문에 “rtmfp://stratus.adobe.com/개발키” 에 접속해서 언제든지 개발은 가능하다. 아래는 개발키를 가지고 Adobe의 Stratus서버에 접속인증을 하는 예제이다.
이 그림그리기 툴은 그래픽 관련 API를 사용하고, 로컬에 저장하기 위해 Flash Player 10 FileReference API를 사용한다. 만약 Flash Player 9이하라면 이 프로그램은 정상동작하지 않는다. Flash Player 10 부터 로컬에 Flash Player에서 만들어진 리소스(바이너리 데이타, 텍스트 데이타 모두)를 사용자의 인터렉션이 있다는 조건하에 저장이나 읽기가 가능해졌다. 이 프로그램은 단순히 그림그리는 툴을 만들었다는 내용보다는 이것을 강조한 것이다. Flash Player 9 이하의 버전이라면 이것을 구현하기 위해 반드시 서버로 이미지를 저장한 다음에 그 이미지를 가져오는 단계가 필요하다.
100줄도 안되는 MXML코드로 구현한 것이니 내용을 참고한다면 도움이 될 것이라 생각한다.
Stratus는 Flash Player간 RTMFP(Real-Time Media Flow Protocal)을 이용해 Peer-to-Peer 통신을 가능하게 해주는 신기술입니다. 최소한의 서버 통신을 이용해 Flash Player간에 직접적 통신이 가능하도록 하여 서버부하를 줄여줄 수 있다는데 큰 매력을 느끼게 합니다. TCP가 아닌 UDP 기반으로 통신하기 때문에 훨씬 속도가 빠르겠군요.
이제 Flash Player 간 인터넷전화, 메신저, 화상채팅등이 구현이 되겠군요. 지속적으로 관심을 가지고 지켜봐야할 기술임에 틀림없겠습니다.
2. 다운로드 받는 Flex 4 SDK를 Flex Builder 설치폴더안에 sdks 폴더에 압축하여 풀어준다.
3. Flex Builder 3를 실행해서 메뉴에 Windows->Preperences...에 들어가면 창이 뜬다. 창 왼쪽에 Flex->Installed Flex SDKs를 선택한 다음 아까 압축을 푼 SDK폴더를 등록한다.
4. File->New->ActionScript Project를 선택하면 창이 뜬다. 창 아래부분에 Use a specific SDK를 선택한다음 Flex 4를 선택하고 프로젝트 명을 CubeRotationExample로 프로젝트를 만든다.
5. 아래 소스를 복사해서 메인 소스에 덮어쓴다.
package{
import flash.display.*;
import flash.events.*;
import flash.geom.*;
public class CubeRotationExample extends Sprite {
private static const EDGE :Number = 40; //edge length
private var mtxWorld :Matrix3D; //world transform matrix
private var vCube :Vector.<Number>; //cube points
private var vFlatCube :Vector.<Number>; //projected cube points
private var vUVT :Vector.<Number>; //uvt data
private var vIndicies :Vector.<int>; //triangle indicies
private var sprCube :Sprite; //canvas object
public function CubeRotationExample(){
// setup and initialize
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.quality = StageQuality.BEST;
mtxWorld = new Matrix3D();
vCube = Vector.<Number>([ EDGE,EDGE,-EDGE,
EDGE,-EDGE,-EDGE,
-EDGE,-EDGE,-EDGE,
-EDGE,EDGE,-EDGE,
EDGE,EDGE,EDGE,
EDGE,-EDGE,EDGE,
-EDGE,-EDGE,EDGE,
-EDGE,EDGE,EDGE
]);
vFlatCube = new Vector.<Number>(16);
vUVT = new Vector.<Number>(24);
vIndicies = Vector.<int>([0,1,2, 2,3,0, 4,7,6, 6,5,4, 0,4,5, 5,1,0, 1,5,6, 6,2,1, 2,6,7, 7,3,2, 4,0,3, 3,7,4]);
sprCube = new Sprite();
addChild(sprCube);
addEventListener(Event.ENTER_FRAME, onEnterFrame);
}
private function onEnterFrame(eo:Event):void {
// center the cube
sprCube.x = stage.stageWidth/2;
sprCube.y = stage.stageHeight/2;
// rotate the transform matrix
mtxWorld.appendRotation(2, new Vector3D(0,1,0));
mtxWorld.appendRotation(2, new Vector3D(0,0,1));
mtxWorld.appendRotation(2, new Vector3D(1,0,0));
// project 3D data to 2D plane
Utils3D.projectVectors(mtxWorld, vCube, vFlatCube, vUVT);
// draw the cube using cube data
sprCube.graphics.clear();
sprCube.graphics.beginFill(0xFF00FF);
sprCube.graphics.lineStyle(1,0xFF0000,1);
sprCube.graphics.drawTriangles(vFlatCube, vIndicies, null, TriangleCulling.POSITIVE);
sprCube.graphics.endFill();
}
}
}
6. 저장하면 에러가 날거다. Flash Player 10에서 작동하므로 그에 따른 설정을 해주어야 한다. 메뉴에서 Project->Properties 로 들어가면 창이 뜬다. 왼쪽메뉴에 ActionScript Compiler를 선택하고 오른쪽 아래 Require Flash Player version을 10.0.0으로 맞춰준다.
7. 실행한다.(단 Flash Player 10이 설치되어 있어야 한다.)
[실행화면]
다시한번 언급하지만 Flash Player 10 환경에서만 된다.
위의 소스를 보면 예전 ActionScript 3.0에서 볼 수 없었던 것들이 보인다. Vector, Matrix3D, Util3D등이다. Papervision3D와 같은 것을 쓰지않아도 쉽게 네이티브 코드로 3D를 구현할 수 있게 한 Flash Player 10. 아직 다양한 표현을 위한 많은 API를 가지고 있지 않지만 더욱 발전될 가능성이 크다고 생각한다. 기대해보겠다.
POST방식으로 MultiPart 컨텐츠 타입으로 지정하여 데이타를 서버에 올리는 경우가 종종 있다.
가령, 이미지를 BitmapData로 캡쳐해서 ByteArray형태로 만든다음 서버에 전송하기 위해서는 URLLoader를 이용해 URLRequest의 contentType을 "multipart/form-data;"으로 지정해서 전송할 수 있다. 이와 관련되서는 예전에 "[Flex/AIR]BitmapData를 PNG나 JPG로 변환하여 ByteArray로 서버에 전송하는 방법" 제목으로 글을 올린적 있으니 참고바란다.
하지만 Flash Player 10부터 이러한 과정에서 보안이슈에 걸린다. 아래는 Flash Player 10 기준 ActionScript 3.0 메뉴얼에서 URLLoader의 load()에 설명되어 있는 글이다.
Flash Player 10 이상에서는 multipart Content-Type(예: "multipart/form-data")을 사용하고 POST 본문 내 "content-disposition" 헤더에 "filename" 매개 변수를 지정하여 업로드를 처리하는 경우 POST 작업이 업로드에 적용되는 보안 규칙의 영향을 받을 수 있습니다.
POST 작업은 마우스 클릭이나 키 누르기 같은 사용자 동작에 대한 응답으로 수행됩니다.
POST 작업이 크로스 도메인인 경우, 즉 POST 대상이 POST 요청을 보내는 SWF 파일과 같은 서버에 없는 경우 대상 서버는 크로스 도메인 액세스를 허용하는 URL 정책 파일을 제공해야 합니다.
또한 multipart Content-Type의 경우 구문이 RFC2046 표준에 따라 유효해야 합니다. 구문이 유효하지 않은 경우 POST 작업은 업로드에 적용되는 보안 규칙의 영향을 받을 수 있습니다.
마우스 클릭이나 키 누르기 같은 사용자 동작에 대한 응답이 있은 직후에 하지 않고 이 기능을 사용하게 되면 "SecurityError: Error #2176 Certain actions, such as those that display a pop-up window, may only be invoked upon user interaction, for example by a mouse click or button press." 에러가 발생한다. 이 메시지는 자바스크립트(Flash가 아닌 외부)에서 Flash 컨텐츠의 FileReference browse() 메소드를 같은 이유의 보안 문제로 호출할 수 없을때와 동일하다. 이와 관련되어서는 다음 글을 참고한다.