openGL, GLUT dll, header, lib 파일 묶음

매번 찾으려니깐 귀찮아서 올려 놓는다. ㅎㅎ

Posted by Junios

2010/08/19 13:35 2010/08/19 13:35
,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/263

유니코드 한글 표

브리오에서 한글 폰트 만드려면 유니코드 표가 필요해서 만들어 놓자.

Posted by Junios

2010/01/07 16:00 2010/01/07 16:00
,
Response
A trackback , No Comment
RSS :
http://junios.net/tc/rss/response/255

임의축으로 회전 행렬

x = 임의 정규화된 축의 x 값
y = 임의 정규화된 축의 y 값
z = 임의 정규화된 축의 y 값
 t = 회전 각 (세타, 라디안값)

 [(x * x * (1 - cos(t)) + cos(t))   (x * y * (1 - cos(t)) + z * sin(t))  (x * z * (1 - cos(t)) - y * sin(t))]
 [(x * y * (1 - cos(t)) - z * sin(t))  (y * y * (1 - cos(t)) + cos(t))   (y * z * (1 - cos(t)) + x * sin(t))]
 [(x * z * (1 - cos(t)) + y * sin(t))  (y * z * (1 - cos(t)) - x * sin(t))  (z * z * (1 - cos(t)) + cos(t))]

이란다.

공식 유도는 잘 ..

Posted by Junios

2009/08/25 11:47 2009/08/25 11:47
,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/252

Maxscript 관련

이 문서는 제가 3D Studio Max 3.x SDK 프로그래밍을 하면서 제가 필요한 것들만 정리
한것입니다. 저만 알아볼 수 있도록 되어 있는 부분이 많습니다. 시간날 때 다시 정리
하겠습니다.

--------------------
SDK 관련 사이트
--------------------
http://www.discreet.com/support/max/download/download.php3 : SDK 다운로드
http://sparks.discreet.com/downloads/downloadshome.cfm : SDK 관련 각종 다운로드
http://sparks.discreet.com/search/
http://support.ktx.com/~200/
http://sparks.discreet.com/webboard/wbpx.dll/~maxsdk/
http://support.discreet.com/webboard/wbpx.dll/%7Emos
http://www.gamasutra.com/features/19980220/baumann_01.htm
http://www.scriptspot.com : max sciprt 관련

--------------------
MAX SDK 관련 서적
--------------------

Maxscript and the Sdk for 3d Studio Max
 : http://www.amazon.com/exec/obidos/ASIN/0782127940/

--------------------
TimeValue
--------------------

  Max 에서 시간표현은 TimeValue 라는 타입으로 사용한다. 이것은 ticks 라는 단위의
  정수표현인데, 1초는 4800 ticks 에 해당한다. 이값은 아래의 표준 프레임수에 근거
  하여 모두 나누어 질 수 있는 적당한 값을 선택한 것이다.
  
  24 - 필름, 25 - PAL, 30 - NTSC
  
  TimeValue start = ip->GetAnimRange().Start();
  TimeValue end = ip->GetAnimRange().End();
  int number_of_frames = (end - start) / GetTicksPerFrame() + 1;
  int frame_per_second = GetFrameRate(); // = 4800 / GetTicksPerFrame()
  
--------------------
Transformation Matrix Order
--------------------

  Max 에서 TM(Transform Matrix)는 Matrix3 클래스로 정의된다. 이는 흔히 말하는
  행렬을 의미한다. 다른 점이 있다면 4x3 행렬이라는 것이다. 내부적으로는 
  float m[4][3]; 처럼 정의된 데이타타입을 가지고 있다. Max에서 모든 벡터는 
  행벡터로 간주되기 때문에 벡터-행렬곱은 다음과 같은 식으로 이루어진다.
  
  [v0 v1 v2 v3] * | m00 m01 m02 m03 | = [ v0' v1' v2' v3' ]
                  | m10 m11 m12 m13 |
                  | m20 m21 m22 m23 |
                  | m30 m31 m32 m33 |
  
  (* m00, m01,...mij 등등의 표현은 i번째 행, j번째 열의 행렬요소를 의미한다. 
     배열이라고 하면 m[i][j]라고 표현할 수 있다)
   
  오픈지엘에서는 아래와 같은 열벡터를 사용한다. 유의할 점은 맥스에서와는 달리 벡
  터가 곱하려는 행렬의 우측에 위치해야 한다는 것이다.
  
  | m00 m01 m02 m03 | * | v0 | = | v0' |
  | m10 m11 m12 m13 |   | v1 |   | v1' |
  | m20 m21 m22 m23 |   | v2 |   | v2' |
  | m30 m31 m32 m33 |   | v3 |   | v3' |
  
  하지만, 맥스에서는 vector * TM 뿐만 아니라, TM * vector 도 허용한다는 사실을
  알아두자(결과는 동일하다).
  
  맥스에서 사용하는 행렬은 앞서 말한것처럼 4x3 행렬이기 때문에 일반적인 행렬의
  w(scale)성분이 없다. 행렬이 아래와 같은 식으로 구성된다.
  
  | m00 m01 m02 |
  | m10 m11 m12 |
  | m20 m21 m22 |
  | m30 m31 m32 |
  
  만약, [v0 v1 v2] 인 벡터와의 형렬곱은 내부적으로 아래와 같이 계산될 것 같다.
  
  [v0 v1 v2 1] * | m00 m01 m02 | = [v0' v1' v2']
                 | m10 m11 m12 |  
                 | m20 m21 m22 |
                 | m30 m31 m32 |
  
  행벡터를 사용하는 맥스의 행렬을 열벡터를 사용하는 OpenGL에서 사용하려면 행렬을
  전치(Transpose)시켜야 한다.
  
    (맥스:행벡터사용)     (오픈지엘:열벡터사용)
  | m00 m01 m02 N/A |     | m00 m10 m20 m30 |
  | m10 m11 m12 N/A | =>  | m01 m11 m21 m31 |
  | m20 m21 m22 N/A |     | m02 m12 m22 m32 |
  | m30 m31 m32 N/A |     | N/A N/A N/A N/A |
  
  정의되어 있지 않은 [N/A N/A N/A N/A] 는 [0 0 0 1] 로 대입하여야 하며, 오픈지엘
  은 행렬을 배열로 표현시에 아래와 같은 순서를 취하기 때문에,
  
  | g0 g4 g8  g12 |   | g00 g10 g20 g30 |
  | g1 g5 g9  g13 | = | g01 g11 g21 g31 |
  | g2 g6 g10 g14 |   | g02 g12 g22 g32 |
  | g3 g7 g11 g15 |   | g03 g13 g23 g33 |
  
  맥스의 m배열은 OpenGL의 g배열로 아래와 같이 매핑된다.
  
  | m00 m10 m20 m30 |    | g00 g10 g20 g30 |
  | m01 m11 m21 m31 | =  | g01 g11 g21 g31 |
  | m02 m12 m22 m32 |    | g02 g12 g22 g32 |
  |   0   0   0   1 |    | g03 g13 g23 g33 |
  
  운좋게도, MAX 의 m[i][j] 의 값은 OpenGL 의 g[i][j] 와 같다. g[0][3] - g[3][3]
  의 값이 [0 0 0 1] 인 점만 제외하면 나머지 값은 MAX 배열 순서와 일치한다.
  코딩을 한다면 아래와 같을 것이다.
  
  for (i = 0; i < 4;i++) {
    for (j = 0; j < 3;j++) {
      OpenGL[i][j] = Max[i][j];
    }
    OpenGL[i][3] = 0.0;
  }
  OpenGL[3][3] = 1.0;
  

--------------------
Transformation Matrix
--------------------

o Object Offset TM
  Object Offset이란 맥스의 Pivot 축을 기준으로 하여 지오메트리가 위치하는 TM을
  말한다. 맥스에서 Pivot을 움직이거나 회전시킬 수도 있는 것은 이 때문이다.
  아래와 같이 계산된다.
  
  Object Offset TM = Offset Scale * Offset Rotation * Offset Position
 
o NodeTM
  NodeTM이란 Pivot 축을 월드좌표로 변환하기 위한 TM이다.
  
o Object TM
  Object TM이란 오브젝트의 한 점을 월드좌표계로 변환하기 위한 TM을 의미한다.
  ObjectTM은 부모TM, NodeTM, ObjectOffsetTM을 모두 포함한다.
  
  ObjectTM = Offset Scale * Offset Rotation * Offset Position * 
             Controller Scale * Controller Rotation * Controllers Position * 
             Parent Transformation

o Local Transformation
 
  NodeWorldTM = NodeLocalTM * ParentLocalTM * ParentLocalTM * ParentLocalTM ...
  
  ParentWorldTM = ParentLocalTM * ParentLocalTM * ParentLocalTM ...
  
  NodeWorldTM = NodeLocalTM * ParentWorldTM
  
  NodeLocalTM = NodeWorldTM * Inverse(ParentWorldTM)
  
  예제)
  
  ...
  INode *parent;
  Matrix3 parentTM, nodeTM, localTM;
  nodeTM = node->GetNodeTM(0);
  parent = node->GetParentNode();
  parentTM = parent->GetNodeTM(0);
  localTM = nodeTM*Inverse(parentTM);
  ...

--------------------
MAX 3.0 을 Windows 2000 (한) 에서 실행하기
--------------------

  Max 3.1 에서는 상관없지만 3.0 버젼을 Windows 2000 (한글) 에서 사용하려면 
  다음과 같은 옵션을 주어 실행한다.
  
  C:\3DSMAX3\3dsmax.exe -q
  
  Max 실행화일의 바로가기 아이콘을 만든다음 등록정보의 "대상"에서 실행옵션을 
  위와 같이 적어주면 된다.
  
--------------------
SDKLINK.ZIP
--------------------

  MAXSDK/HELP 디렉토리를 보면 SDKLINK.ZIP 라는 파일이 있다. 압축을 풀고 .DOC 
  파일을 보면 알 수 있겠지만, 이 파일은 비주얼 씨를 사용할 때에 TOOL 에 등록하여 
  헬프를 쉽게 사용하기 위한 방법을 설명해 주고 있다. 예를 들면, SDK 헬프에서 
  찾고자 하는 클래스 이름이나 함수명을 선택하고 Ctrl+Alt+C 를 누르면 해당하는 
  MAXSDK 헬프창이 뜨도록 할 수 있다. 물론 울트라에디트나 에디트플러스 등에서도 
  할 수 있다.

--------------------
Class Mesh
--------------------

[from MAX SDK Help]

- UVVert *tVerts;

  텍스쳐 버텍스 배열. UVW 좌표를 저장한다. 2D 매핑을 위해서는 2개의 값만 필요하 
  다. 예를 들면. UV 또는 VW, WU. 이는 사용자에게 UV, VW 또는 WU 중에서 선택할 
  수 있도록 하기 위함이다.

(UVVert 의 정의 : typedef Point3 UVVert;)

- TVFace *tvFace;

  3D 스튜디오(도스용) 시절에는 UV 배열과 버텍스 배열사이에 일대일대응이 되었으 
  다(즉, 각각의 버텍스에 대해서 하나의 UV만). 하지만, 맥스에서는 UVVert 배열은 
  배열과 완전히 독립적이다. 대신에, 별도의 TVFace 배열이 있다. 모든 면은 하나의 
  TVFace 를 가지며, 하나의 TVFace 는 UVVert 를 가리키는 세개의 인덱스가 
  필요하다. 이렇게 한 이유는 개발자가 매핑에 따라 완전히 다른 토폴로지를 적용할 
  수 있도록 하기 위함이다. 객체 각각의 면은 각자 자신의 매핑을 가질 수 있다. 
  (다시 말하면, 인접한 서로 다른 면의 버텍스에 대해서 연속적인 텍스쳐 매핑 
  좌표를 갖지 않아도 된다는 것이다.)     
  
--------------------
Object Creation 
--------------------
- utiltest.cpp -> MakeObject()

1. (Object * ip)->CreateInstance(CLASSID); // CLASSID from PLUGAPI.H
2. ParameterBlock->SetValue
3. CreateObjectNode()
4. Redraw()

--------------------
Rotate
--------------------
- DegToRad (45.0f)
  angle 은 radian 이므로 degree 로 바꿀것

--------------------
TM
--------------------
- A라는 TM에서 b만큼 떨어진 B라는 TM 구하기 (from camera.cpp)

  bM = aM;
  bM.PreTranslate(Point3(0.0f, 0.0f, b));
  
--------------------
Node 다루기
--------------------
 INode * node;
 TSTR name(_T("MyNode"));
 ip->MakeNameUnique(name);
 node->SetName(name); // Node 이름 바꾸기
 ip->RedrawViews(ip->GetTime());
 
 INode *parent;
 Matrix3 parentTM, nodeTM, localTM;
 nodeTM = node->GetNodeTM(0);
 parent = node->GetParentNode();
 parentTM = parent->GetNodeTM(0);
 localTM = nodeTM * Inverse(parentTM);
 ptm = node->GetParentTM(ip->GetTime());
 
 INode * GetINodeByName (const TCHAR *name) = 0;

--------------------
INode Method
--------------------

  Matrix3 GetNodeTM (TimeValue t, Interval * valid = NULL);
    ; 노드의 pivot 의 월드 TM 구하기
  
  Matrix3 GetObjectTM (TimeValue time, Inverval * valid = NULL);
    ; ObjectOffsetTM + NodeTM + WSM
  
  Point3 VectorTransform (const Matrix3& M, const Point3& V);
 
--------------------
대화상자 폰트 작게
--------------------
regedit.exe -> HKEY_CURRENT_CONFIG -> Software -> Fonts -> FONTS.FON 에서

hvgasys.fon 를 vgasys.fon 으로 변경 후 리부팅.

http://www.cgmate.com/zeroboard/zboard.php?id=cgmate_master&no=20

--------------------
플러그인이 로딩 안될 때
--------------------
작성한 플러그인이 어떤 시스템에서는 로딩되지만, 어떤 시스템에서는 로딩되지 않을
때에는, 컴파일시 사용한 .DLL 파일이 없어서일 수 있다. 
예를 들어, .net 환경에서 컴파일하면, msvcr70.dll 을 사용하게 되는데, 이 파일이 
없는 시스템에서는 dll이 동작하지 않는다.
플러그인 파일이 어떤 DLL을 사용하는 지 확인하려면, 비주얼씨의 bin 디렉토리에 있는,
dumpbin.exe 를 사용하여 확인한다. 

> dumpbin.exe /imports utility.dlu

--------------------
max script 실행
--------------------

스크립트는 Max 메뉴의 MAXScript->Run Script...를 통해 실행할 수 있으며, 간단한 
스크립트라면, 좌측 하단의 미니 리스너를 통해 입력하는 것도 좋다. F11키나 
MAXScript->MAXScript Listener...를 선택하여 나타내는 리스너 윈도우는 뷰포트 
메뉴의 Views->Extended->MAX Script Listener를 선택하면 뷰포트 내에 내장시킬 수도 
있다.

--------------------
Visual MAXScript
--------------------

MAXScript메뉴의 Visual MAX Script Editor...를 선택하면 스크립트 롤아웃의 사용자 
인터페이스 컨트롤 들을 마음대로 배치할 수 있는 툴이 실행된다. 이 툴을 사용하면 
버튼이나, 리스트 박스 등의 롤아웃 컨트롤 들을 자유롭게 배치할 수 있으며, .ms 
파일인 스크립트 파일로 저장할 수도 있다. 저장된 스크립트 파일은 다음과 같은 
형식을 가지며, 컨트롤들의 넓이와 높이 위치가 지정된 것을 확인할 수 있다.

rollout unnamedRollout "Untitled" width:215 height:314
(
	comboBox cbx1 "ComboBox" pos:[15,15] width:130 height:3
	listBox lbx1 "ListBox" pos:[16,88] width:81 height:2
	pickButton btn1 "PickButton" pos:[21,161] width:59 height:20
	checkButton ckb1 "CheckButton" pos:[98,159] width:80 height:29
	button btn2 "Button" pos:[21,209] width:62 height:31
	button btn3 "Button" pos:[102,204] width:64 height:35
)

--------------------
Mesh관련 max script 명령
--------------------

convertToMesh <node>
  : 씬 객체를 Editable Mesh로 변환한다.
<mesh>.numverts
  : 버텍스 개수를 얻거나 설정한다.
<mesh>.numfaces
  : 페이스 개수를 얻거나 설정한다.
<mesh>.numtverts
  : 텍스쳐 버텍스 개수를 얻거나 설정한다.

--------------------
UltraEdit 에디터에서 max script 사용하기
--------------------

맥스자체에서 제공하는 스크립트 편집기를 사용하면 맥스 자체에서 제공하는 몇 가지 
이점이 있기는 하지만, 탭 인덴테이션이 제공되지 않아, 매 라인마다 탭 키를 눌러 야 
하는 불편함이 있다. 다행히 울트라 에디트에서 사용할 수 있는 max script용 
워드파일을 누군가 미리 만들어 놓았기 때문에, 이를 사용하면 울트라 에디트에서도 
maxscript를 위한 신택스 컬러링이라든가, 탭 인덴테이션을 사용할 수 있다.

아래의 주소에서 파일을 다운받아, 자신의 울트라에디트 설치폴더에서 wordfile.txt 
에 덧붙여 주면 된다.

ftp://ultraedit.com/wf/maxscript.txt

--------------------
max script로 다중 맵 채널 데이터 뽑아내기
--------------------

Max 3.0 이상부터는 텍스쳐 좌표를 여러 개 가질 수 있다. 하나의 메쉬에 여러 채널의 
텍스쳐 좌표를 가질 수 있게 되면, 건물의 라이트 맵에 대한 uv좌표와 디퓨즈 맵에 
대한 uv좌표를 다르게 가질 수 있다. 다중 채널 데이터를 뽑기 위한 명령으로 다음과 
같은 명령들이 있다.

meshop.setNumMaps / meshop.getNumMaps
  : 메쉬의 맵 채널을 얻거나 설정한다. 기본적으로 2개의 매핑 채널을 가지는데, 
  1번과 2번 채널이 버텍스 색상과 기본 텍스쳐 맵 채널로 사용된다.  
meshop.setMapSupport / meshop.getMapSupport
  : 해당 맵 채널이 사용되고 있는 지를 알려 주거나, 사용함을 알린다.
meshop.setNumMapVerts / meshop.getNumMapVerts
  : 맵 채널에 할당된 버텍스 개수를 설정하거나 얻는다. 
meshop.setMapVert / meshop.getMapVert
  : 맵 버텍스를 설정하거나 얻는다. 맵 버텍스는 Point3구조로서 통상 uv좌표가 
  들어가게 된다.
meshop.setMapFace / meshop.getMapFace
  : 맵 페이스를 설정하거나 얻는다. 맵 페이스에도 Point3구조이나 맵 버텍스에 대한 
  인덱스가 들어간다. 물론, 인덱스는 1부터 시작한다.  
meshop.setNumTVerts / meshop.getNumTVerts
  : 일반 텍스쳐 맵 채널 버텍스 개수를 얻거나 설정한다. set/getNumMapVerts 의 2번 
  채널을 사용한 것과 같은 결과이다.

--------------------
maxscript로 머티리얼 정보 뽑아내기
--------------------
맥스에서 메쉬의 face별로 재질을 다르게 선택하려면, Multi/Sub 재질을 만들어서 
하는 것이 정석이지만, 그렇게 하지 않았더라도, 내부적으로는 Multi/Sub  재질이 
하나 만들어집니다. 다음의 코드는 각 face 단위로 재질의 이름을 출력하는 
코드입니다.

for o in objects do 
( 
    faceCount = o.numfaces 
    for k = 1 to do 
    ( 
        mtlid = (getFaceMatID o k) 
        -- if no diffuse map enabled, skip 
        if (o.material[mtlid].maps[2] == undefined) then continue 
        -- if no diffuse texture exists, skip 
        if (o.material[mtlid].maps[2].filename == undefined) then continue 
        
        -- get the diffuse map filename
        texture_full_path = o.material[mtlid].maps[2].filename
        texture_name = filenameFromPath texture_full_path
        format "texture_name = %\n" texture_name
    ) 
) 

--------------------
maxscript 로 피지크 정보 뽑아내기
--------------------
캐릭터 스튜디오 3.x 에서 피지크에 대한 정보를 스크립트로 뽑아내려면,

1. 아래의 사이트에서 IPhysique.zip를 다운받는다.
http://www.max3d.com/plugins/r4/IPhysique.zip
http://sparks.discreet.com/downloads/downloadshome.cfm?f=2&wf_id=65
(등록을 해야 다운받을 수 있지만, 등록은 공짜이며 등록할 가치 충분히 있음)

2. IPhisique.gup 파일을 /plugins 디렉토리에 복사한다.

3. 사용법은 동봉된 IPhysique.doc 파일에 있다.

4. 간단한 사용예.
> physiqueops.getAPIversion $box01

--------------------
max sciprt로 피지크 정보 빼내오기
(IPhysique 사용)
--------------------
-------------------------------------
-- physique script extract example --
--
-- 다음의 플러그인이 필요하다.
-- 
-- http://sparks.discreet.com/downloads/downloadshome.cfm?f=2&wf_id=65
-- (또는 http://www.max3d.com/plugins/r4/IPhysique.zip )
--
-- by zho@korea.com
-- 2002.12.26
-------------------------------------

function extract_physique_info physiqued_obj =
(
  po = physiqued_obj
  pm = physiqueOps.getPhysiqueModifier po
  ver = physiqueOps.getAPIVersion po modifier:pm
  bone_arr = physiqueOps.getBones po modifier:pm
  vertex_count = physiqueOps.getVertexCount po modifier:pm
  format "총 버텍스 개수 : %\n" vertex_count
  for v_index = 1 to vertex_count do
  (
    -- check vertex type
    -- physiqueOps.getVertexType po v_index modifier:pm
    format "버텍스 번호[%] : " v_index
    affected_bone_count = physiqueOps.getVertexBoneCount po v_index modifier:pm
    format "총 %개의 본에 의해 영향받음\n" affected_bone_count
    for b_index = 1 to affected_bone_count do
    (
      bone_node = physiqueOps.getVertexBone po v_index b_index modifier:pm
      weight = physiqueOps.getVertexWeight po v_index b_index modifier:pm
      offset = physiqueOps.getVertexOffset po v_index b_index modifier:pm
      format "\t본정보 : 인덱스[%]-" b_index
      format "이름[%]-" bone_node.name
      format "가중치[%]-" weight
      format "오프셋[%]\n" offset
    )
    format "\n"
  )
)

extract_physique_info $Box01 -- sample test

---------- 실행 결과 -------------------
총 버텍스 개수 : 66
버텍스 번호[1] : 총 1개의 본에 의해 영향받음
  본정보 : 인덱스[1]-이름[Bip01 Pelvis]-가중치[1.0]-오프셋[[-46.969,-32.857,5.82977]]

버텍스 번호[2] : 총 1개의 본에 의해 영향받음
  본정보 : 인덱스[1]-이름[Bip01 R Foot]-가중치[1.0]-오프셋[[5.73779,0.0505373,6.23943]]
............
버텍스 번호[66] : 총 2개의 본에 의해 영향받음
  본정보 : 인덱스[1]-이름[Bip01 R Calf]-가중치[0.973368]-오프셋[[15.982,-5.13998,-4.0716]]
  본정보 : 인덱스[2]-이름[Bip01 L Calf]-가중치[0.0266323]-오프셋[[15.982,-5.13998,5.57165]]
  
--------------------
maxscript로 Limb IK 흉내낸 코드
--------------------
-------------------------------------- begin of iktest.ms
--------------------------------------
-- Simple Limb Inverse Kinematics Test
-- by Jiho Choi (zho@korea.com)
-- from [ http://zho.pe.kr ]
-- 2002.10.28
--------------------------------------

resetMAXFile()

-- object creation
global initial_hip_rotation
global initial_knee_rotation

bone pos:[0, 0, 0] name:"hip"
bone pos:[103.707,-7.60097,0] name:"ankle"
bone pos:[56.633,-1.67554,0] name:"knee"
bone pos:[49.3482,-52.7234,0] name:"target"

-- construct parent-child hierarchy
$ankle.parent = $knee
$knee.parent = $hip

-- rotate segment by a given knee angle
fn rotate_segment segment angle_axis =
(
    local local_pos = (in coordsys parent segment.pos)
    in coordsys parent segment.pos -= local_pos -- align to segment's origin
    in coordsys parent segment.rotation = angle_axis
    in coordsys parent segment.pos += local_pos
    in coordsys local segment.rotation = (quat 0 0 0 1)
)

-- compute rotation by from_vector and to_vector
-- returns AngleAxis value
fn get_rotation_from_vectors from_vector to_vector =
(
    from_vector = from_vector
    to_vector = to_vector
    axis_vector = normalize (cross from_vector to_vector)
    angle = acos (dot (normalize from_vector) (normalize to_vector))
    return (angleAxis angle axis_vector)
)

-- get knee rotation axis
fn get_knee_axis =
(
    knee_to_ankle = $ankle.pos - $knee.pos
    knee_to_hip = $hip.pos - $knee.pos
    knee_axis = normalize (cross knee_to_ankle knee_to_hip)
    -- format "knee_axis = %, " knee_axis
    return knee_axis
)

-- rotate hip segment directing to end-effector
fn rotate_hip =
(
    rot = get_rotation_from_vectors $ankle.pos $target.pos
    $hip.rotation = rot
)

fn get_knee_angle =
(
    -- L^2 = L1^2 + L2^2 - 2*L1*L2*cos(theta)
    l1 = distance $knee.pos $hip.pos
    l2 = distance $ankle.pos $knee.pos
    L = distance $target.pos $hip.pos
    if (L > (l1 + l2)) do
    (
        L = l1 + l2 -- do not exceed total length
        -- print "cannot reach"
    )
    knee_angle = acos ((l1*l1 + l2*l2 - L*L) / (2*l1*l2))
    -- angle constraint
    -- if (knee_angle < 10) do knee_angle = 10 -- minimum
    -- if (knee_angle > 170) do knee_angle = 170
    -- current knee_angle means inner angle, thus convert to outer angle
    -- format "knee_angle = %\n" knee_angle
    knee_angle = 180 - knee_angle
    return knee_angle
)

fn rotate_knee =
(
    knee_angle = get_knee_angle() - initial_knee_rotation.angle
    -- knee_axis = get_knee_axis()
    knee_axis = initial_knee_rotation.axis
    rotate_segment $knee (angleAxis knee_angle knee_axis)
)

-- from initial configuration, compute knee axis for later use
initial_hip_rotation = $hip.rotation
initial_knee_rotation = get_rotation_from_vectors $knee.pos ($ankle.pos - $knee.pos)

viewport.activeViewport = 1
viewport.setType #view_top
max tool maximize
max move
select $target
when transform $target changes do
(
    $hip.rotation = initial_hip_rotation
    rotate_knee()
    rotate_hip()
)

-------------------------------------- end of iktest.ms








출처 http://zho.pe.kr/view.html?file_name=doc/maxsdk.txt#maxscript로_Limb_IK_흉내낸_코드












Posted by Junios

2009/05/18 10:59 2009/05/18 10:59
Response
4 Trackbacks , No Comment
RSS :
http://junios.net/tc/rss/response/242

ASE 파일 포맷

Ase 파싱

Ase는 크게 3개의 단위로 이루어져 있다.
각 단위는 씬 정보(*SCENE), 재질 정보(*MATERIAL_LIST), 오브젝트 정보(*GEOMOBJECT)이며,
오브젝트 정보의 경우 추가로 몇 종이 더 있는데 주로 *HELPEROBJECT를 들 수 있다.

이하 세부적으로 꼭 필요한 토큰만 언급하겠다.


//////////////////////////////////////////////////////////////////////////////////////
*SCENE
애니메이션과 관련된 정보를 담는다.

 *SCENE_FIRSTFRAME 0
 애니메이션의 시작 프레임
 *SCENE_LASTFRAME 100
 애니메이션의 마지막 프레임
 *SCENE_FRAMESPEED 30
 초당 프레임
 *SCENE_TICKSPERFRAME 160
 프레임당 틱
 
 애니메이션 1초당 틱 = 초당 프레임 * 프레임당 틱
 대략 4800이며, 애니메이션 보간을 위한 기준값으로 사용된다.
 

//////////////////////////////////////////////////////////////////////////////////////
*MATERIAL_LIST
재질 및 텍스처에 관한 정보를 담는다. 토큰이 많지만 실제로 쓰이는 것은 극히 일부이다.
각 재질은 서브 재질을 가질 수 있다. 서브 재질은 또 서브 재질을 가질 수 있는 구조이지만
실제로 그러한 구조가 쓰이지도 않으며 허용되서도 안 된다.
각 재질은 오브젝트 별로 참조하며, 서브 재질은 페이스 별로 참조하게 된다.

 *MATERIAL_COUNT 1
 총 재질 수
 *MATERIAL 0 {
 번호에 해당하는 재질의 묶음
  *MATERIAL_NAME "Material #2"
  재질 이름: 필요없지만, 필요할지도...
  *MATERIAL_CLASS "Standard"
  *MATERIAL_CLASS "Multi/Sub-Object"
  재질 클래스: 현재 재질이 서브 재질을 포함하는지 여부를 나타낸다. "Standard"는 없고, "Multi..."는 있다.
  지금으로써는 필요없다.
  *MATERIAL_AMBIENT 0.1000 0.1000 0.1000
  *MATERIAL_DIFFUSE 0.7000 0.7000 0.7000
  *MATERIAL_SPECULAR 0.9000 0.9000 0.9000
  앰비언트, 디퓨즈, 스펙큘라값
  *NUMSUBMTLS 15
  현재 재질이 가진 서브 재질의 개수. 서브 재질이 없으면 이 항목은 존재하지 않는다.
  *SUBMATERIAL 0 {
  번호에 해당하는 서브 재질의 묶음
   // 중복 정보 생략. 재질과 출력되는 정보가 동일하다.
   *MAP_DIFFUSE {
   디퓨즈 맵: 여기서는 텍스처 정보만 필요하다.
    *BITMAP "F:\Turret\Turret\Maps\T_Siren1.bmp"
    텍스처 경로: 절대 경로이기 때문에 파일명만을 얻어와야 한다.
 

//////////////////////////////////////////////////////////////////////////////////////
*GEOMOBJECT
오브젝트에 관한 정보(버텍스, 인덱스, UV 등)를 담는다. 오브젝트는 트리 구조로 이루어질 수 있다.

 *NODE_NAME "Box01"
 현재 오브젝트의 이름이다. 트리 구조에서 서로를 이름으로 참조하므로 중요한 값이다.
 *NODE_PARENT "Box00"
 부모 오브젝트의 이름이다. 계층을 이루지 않거나 지금 오브젝트가 부모라면 없는 항목이다.
  *NODE_TM {
  매트릭스 정보
   *TM_ROW0 1.0000 0.0000 0.0000
   *TM_ROW1 0.0000 1.0000 0.0000
   *TM_ROW2 0.0000 0.0000 1.0000
   *TM_ROW3 0.0000 0.0000 0.0000
   4x3 매트릭스. 로테이션, 스케일, 트랜스폼이 적용되어 있다.
   *TM_POS 0.0000 0.0000 0.0000
   위 행렬의 4행과 같다.
   *TM_ROTAXIS 0.0000 0.0000 0.0000
   로테이션 축
   *TM_ROTANGLE 0.0000
   로테이션 값(회전각)
   *TM_SCALE 1.0000 1.0000 1.0000
   스케일 값
   
   로테이션과 스케일을 행렬에서 분리해내기가 까다로우므로(불가능하지는 않은듯)
   위 두 값을 따로 가지고 있는 편이 편할 수 있다.
   이 값들의 실질적인 사용은 애니메이션을 설명할 때 다룬다.
  }
  *MESH {
  매쉬 정보: 실제 오브젝트를 이루는 값들이다.
   *MESH_NUMVERTEX 8
   매쉬를 이루는 버텍스의 개수
   *MESH_NUMFACES 12
   페이스의 개수
   *MESH_VERTEX_LIST {
   버텍스 리스트
    *MESH_VERTEX    0 -10.0000 -10.0000 0.0000
    번호에 해당하는 버텍스의 좌표. x, z, y 순서이므로 dx에서는 두번째 세번째 값을 바꾸어 주어야 한다.
    당연히 위의 *MESH_NUMVERTEX 개수 만큼 있다.
   }
   *MESH_FACE_LIST {
   페이스 리스트
    *MESH_FACE    0:    A:    0 B:    2 C:    3 AB:    1 BC:    1 CA:    0  *MESH_SMOOTHING 2  *MESH_MTLID 1
    번호에 해당하는 페이스의 정보.
    페이스는 버텍스 3개로 이루어지는데 A:, B:, C: 뒤의 각 숫자가 버텍스 리스트의 해당 버텍스 인덱스이다.
    감는 순서가 dx와 반대이므로 1, 3, 2 순서로 저장하여야 한다.
    가장 뒤의 *MESH_MTLID는 서브 재질의 인덱스이다. 주의할 점은 잘못된 값이 들어갈 수 있으므로 검출 코드가 필요하다.
    재질 정보와 텍스처를 교체하는 것은 비싼 연산이므로 참조하는 서브 재질의 번호로 정렬하여 렌더링한다. 또한 서브 재질이 없어도 이 값은 아무 값이나 존재하므로 서브 재질의 유무도 판별해야 한다.
   }
   *MESH_NUMTVERTEX 12
   텍스처 uv 좌표의 개수
   *MESH_TVERTLIST {
   텍스처 uv 좌표 리스트
    *MESH_TVERT 0 0.0000 0.0000 0.0000
    번호에 해당하는 uv 좌표. 마지막 값은 버린다.
    원점이 좌하단이므로 좌상단이 원점인 dx에서 쓰려면 v좌표를 1.0f-v로 보정해야 한다.
   }
   *MESH_NUMTVFACES 12
   텍스처가 적용된 페이스의 개수
   *MESH_TFACELIST {
   페이스의 uv 좌표 리스트
    *MESH_TFACE 0 9 11 10
    번호에 해당하는 페이스의 uv 좌표 인덱스. 역시 1, 3, 2 순서로 적용하여야 한다.
   }
   *MESH_NUMCVERTEX 0
   버텍스 컬러. 쓸 일이 있을까? 잘 모르겠다.
   *MESH_NORMALS {
   노멀
    *MESH_FACENORMAL 0 0.0000 0.0000 -1.0000
    페이스 노멀. 필요없는 값이지 싶다.
     *MESH_VERTEXNORMAL 0 0.0000 0.0000 -1.0000
     *MESH_VERTEXNORMAL 2 0.0000 0.0000 -1.0000
     *MESH_VERTEXNORMAL 3 0.0000 0.0000 -1.0000
     페이스를 이루는 버텍스의 노멀. 번호는 버텍스의 인덱스이다. 역시 x, z, y이므로 y, z 순서로 바꾸어 준다.
   }
  }
  *TM_ANIMATION {
  애니메이션에 관한 내용은 생략. 복잡한 내용이 연계되어 있으므로 나중에..
   *NODE_NAME "Box01"
   *CONTROL_POS_TRACK {
    *CONTROL_POS_SAMPLE 0 0.0000 0.0000 0.0000
    *CONTROL_POS_SAMPLE 800 7.8125 0.0000 0.0000
   }
   *CONTROL_ROT_TRACK {
    *CONTROL_ROT_SAMPLE 800 -1.0000 0.0000 0.0000 0.2454
    *CONTROL_ROT_SAMPLE 1600 -1.0000 0.0000 0.0000 0.5400
   }
   *CONTROL_SCALE_TRACK {
    *CONTROL_SCALE_SAMPLE 0 1.0000 1.0000 1.0000 0.0000 0.0000 0.0000 0.0000
    *CONTROL_SCALE_SAMPLE 800 1.1563 1.1563 1.1563 0.0000 0.0000 0.0000 0.0000

   }
  }
  *MATERIAL_REF 0
  현재 오브젝트가 참조하는 재질의 번호.
 }


출처 : http://rajent.tistory.com/50

참고 : http://wiki.beyondunreal.com/Legacy:ASE_File_Format

Posted by Junios

2009/03/13 16:18 2009/03/13 16:18
, ,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/241

Command 패턴

[[ Command 패턴 ]]

< 의도 >
객체의 request를 캡슐화해서 각각 다른 request를 매개변수화(parameterize)할 수 있도록 한다. 그래서 그것들을 queue에 집어넣거나 기록(log)하거나,undo등을 지원하도록 한다.

< 동기 >
또다시 스타크래프트의 유닛 클래스를 만드는 상황을 생각해 봅시다. (맨날 스타크래프트의 예만 드는군요. 저는 스타크를 별로 좋아하지 않지만 대중성있고 잘 만들어진 게임이기 때문에 스타크의 예를 드는 것이예요~ ) 

지난번의 상태 패턴에 이어서 유닛 클래스를 만들 경우를 생각해 봅시다. 스타에서 지원하는 웨이포인트를 모르시는 분은 없을 것입니다. 큐에다 이동 명령들을 쌓아놓고 실행하도록 하는 것인데 오직 이동 명령만 넣을 수가 있습니다. 그러나 Earth2140이던가? 그 게임에서는 모든 명령을 큐에다 넣고 실행 할 수 있었던 걸로 기억 되는군요. 작전 명령을 내려놓고, "준비 땅!" 하면 작전 명령에 따라 한 단계씩 수행하는 것입니다. 정말 편리한 기능이죠. 그것을 보고 뚱띵이 빌로퍼가 말했다고 칩시다. "우리도 저 기능을 넣자!! 이게 니 몫이라는건 알고 있겠지?" 
당신은 찍소리도 못하고 만들어야만 할 것입니다. 이것을 구현하려면 Queue가 필요할 것입니다. 그러나 보통의 자료를 저장하는 큐가 아닌 기능을 저장하는 큐가 필요할 것입니다. 그럼 어떻게 기능을 자료처럼 큐에 저장할 것이냐? 아마도 그 방법 중 하나로서 함수포인터가 있을 것입니다. 그러나 지난 강좌에서 말씀 드렸다시피 함수포인터보다 더 많은 장점을 가진 것이 있습니다. 바로 가상함수와 인터페이스 클래스를 이용한 함수포인터의 구현!! 이것을 이용해 Queue, log Undo, list, Tree 등등을 구현하는 것이 바로 이 Command 패턴인 것입니다. 사실 이 가상함수와 인터페이스 클래스를 이용한 함수포인터는 대부분의 패턴의 기본이 됩니다. 이것을 응용해서 만들어진 것들이 이 패턴이라고도 할 수 있고, 이 패턴들이 모두 이것으로 통한다고도 할 수 있습니다. 

< 소스 >
그렇다면!! 이제 Command패턴의 구현에 대해서 알아보도록 합시다요~.

class ICommand //각 명령들의 인터페이스 클래스
{
public:
virtual void Execute() = 0;
};
//전방 참조
class cCommand_Attack;
class cCommand_Move;
class cCommand_Stop;
class cCommand_HoldPosition;
class cCommand_Patrol;

class IUnit
{
IState * _pState; //state패턴 강좌에서 cZealot클래스에 있었던
//변수를 여기로 옮겨옴
public:
//Shift가 눌렸을 때 이 함수가 호출되겠죠.
virtual void ChangeToPlanMode( IUnit *& pPtrUnit )
{
//cPlanDecorator를 붙인다.
new cPlanDecorator( pPtrUnit );
}
//Shift가 떼어졌을 때 이 함수가 호출
virtual void ChangeToNormalMode()
{}

//상태에 대한 ID를 리턴하는 함수( state패턴 강좌에서 IState클래스가
// 수정 되었습니다)
ID_STATE GetStateID()
{
return _pState->GetID();
}
//나머지는 지난번 강좌의 IUnit클래스와 같음
....
....
};

//cUnitDecorator는 지난번 강좌(Decorator)에서 나왔던 그 놈임.
class cPlanDecorator : public cUnitDecorator
{
IUnit * _pActor;
queue< ICommand * > * _pCommandQ; //STL의 queue를 사용
bool _IsInputMode; //명령의 입력 모드인가?(true)
//아니면 실행 모드인가?(false)
public:
cPlanDecorator( IUnit *& pActor )
: cUnitDecorator( pActor ), _pActor( pActor ), _IsInputMode(true)
{}

void ChangeToPlanMode( IUnit *& pPtrUnit )
{
_IsInputMode = true; 
}
void ChangeToNormalMode()
{
_IsInputMode = false;

void ClearPlan()
{
_pCommandQ->clear();
}
void Attack( IUnit * Target ) 

//Shift를 누른 상태로 공격 명령을 내리면 명령 추가
if( _IsInputMode )
_pCommandQ->push( new cCommand_Attack( Target ) );
//Shift를 뗀 상태로 공격 명령을 내리면 큐의 모든 명령이 취소되
//고, 공격 명령을 바로 실행.
else 
{
_pActor->Attack( Target );
SelfDestroy(); 

}
void Move( int x, int y ) 
{
//Attack과 마찬가지..
if( _IsInputMode )
_pCommandQ->push( new cCommand_Move( x, y ) );
else
{
_pActor->Move( x, y );
SelfDestroy();
}
}
void Stop()
{
//Stop명령은 큐에 집어넣을 수 없음.
if( !_IsInputMode )
{
_pActor->Stop();
SelfDestory();
}
}
//HoldPosition, Patrol등은 귀찮으니 생략함..
//일꾼 같으면 Build명령이 있을테고, 마법유닛은 Special명령이 있겠지만
//이것도 모두 위의 Attack이나 Move처럼 처리하면 됩니다.



void Update()
{
//Stop, HoldPosition, Patrol등 무한히 지속되는 명령들이 아닌
//나머지 명령들은 명령수행을 마쳤을 때 Stop상태로 바뀌므로
//Stop상태가 되었을 때 다음 명령을 실행.
if( _pActor->GetStateID() == IState::STATE_STOP )
{
if( !_pCommandQ->empty() )
{
ICommand * pCommand = _pCommandQ->front();
pCommand->Execute();
delete pCommand;
}
else
{
SelfDestroy();
}
}

_pActor->Update();
}
};

class cCommand_Attack : public ICommand
{
IUnit * _pActor;
IUnit * _pTarget;
public:
//주의!! 여기서는 편의상 생성자의 선언과 정의를 한꺼번 
//에 해서 인라인이 되어 버렸지만 생성자를 인라인으로 만 
//드는 것은 좋지 않습니다.자세한건 Effective C++참조
cCommand_Attack( IUnit * pActor, IUnit * pTarget )
: _pActor( pActor ), _pTarget( pTarget )
{
}
void Execute()
{
_pActor->Attack( _pTarget );
}
};

class cCommand_Move : public ICommand
{
IUnit * _pActor;
int _DestX, _DestY;
public:
cCommand_Move( IUnit * pActor, int x, int y )
: _pActor( pActor ), _DestX( x ), _DestY( y )
{ }
void Execute()
{
_pActor->Move( _DestX, _DestY );
}
};

//이런 식으로 사용하게 되겠죠.
void OnKeyDown( int KeyCode )
{
if( KeyCode == SHIFT )
{
IUnit &* pSelectedUnit = GetSelectedUnit();
pSelectedUnit->ChangeToPlanMode( pSeletecdUnit );
}
}

void OnKeyUp( int KeyCode )
{
if( KeyCode == SHIFT )
{
IUnit &* pSelectedUnit = GetSelectedUnit();
pSelectedUnit->ChangeToNormalMode();
}
}

이 외에도 Command 패턴은 많은 곳에서 쓰입니다. 비동기적인 처리를 위해 어떤 메시지를 받고 나서 수행은 나중에 할 때라던가, 우선순위 큐에 넣어서 이벤트 시스템에 쓰이기도 합니다. undo, redo에서도 사용되구요.
Attack과 Move 명령만 구현했는데, 나머지 명령들도 비슷한 방법으로 구현하시면 됩니다.

위에서 만든 명령 예약 기능은 지난 강좌에서 했던 State패턴과 잘 연동되어 돌아갈 것입니다. 유닛이 구체적으로 무엇인지와 관계 없이 그의 인터페이스를 그대로 호출하기 때문이지요. 각 유닛은 Attack, Move 등등의 명령 함수들만 재정의 해주면 되는 것입니다.

위에서 구현한 것과 다른 방식이 있는데, 각각의 Command클래스에 구현 자체를 전부 넣어버리는 방식입니다. 위에서는 Command클래스의 Execute가 호출되면 유닛의 함수를 호출하기만 하는데, (이걸 '대리'한다고 합니다.) 이 '대리'를 하지 않고 Execute함수에다 구현을 해서 필요한 일을 모두 Execute함수 내에서 수행하는 방식입니다.

위의 것을 이해하셨다면 undo나 redo, log등도 만드실 수 있을 것입니다. undo, redo의 예제는 디자인 패턴 책에 나와있으니 필요하신 분은 책을 참고하세요.

이 패턴을 응용하면 다양한 자료구조에 모두 적용할 수 있습니다. tree, list, 우선순위 큐, 해쉬 등등... 


p.s 주의사항!! 제 패턴 강좌의 소스들은 컴파일을 해보지 않은 코드들이기 때문에 실제 사용시에는 문제를 일으킬 수도 있습니다. 때문에 위의 소스 코드는 이해의 목적으로만 사용하시고 복사, 붙여넣기를 해서 사용하진 마세요.

Posted by Junios

2009/02/26 18:03 2009/02/26 18:03
,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/238

Decorator 패턴

[[ Decorator 패턴 ]]

< 의도 >
동적으로 (실행시간중에) 객체에 부가적인 기능을 붙여주고 싶다. 이 패턴은 기능을 확장하기 위해 서브클래싱(상속)을 하는 것에 대한 유연한 대안을 제시해준다.

< 동기 >
..이번에도 스타크래프트 개발 현장..

유닛의 렌더링을 만들어야 하는 상황이라고 생각 해봅시다. 저의강좌에서는 상당히 늦게 언급하게 됐는데, 유닛의 렌더링같은 경우에는 다른 어떤 부분보다도 먼저 만들게 되겠죠.

빌로퍼가 말합니다. "이제는 유닛의 렌더링을 만들어야 한단다. 근데 문제가 그렇게 간단한건 아니고 여러 가지 마법 효과와 상태 효과를 잘 정리정돈해서 출력해야 하기 때문에 문제란다~. 이걸 잘 정리정돈하지 않으면 계속 개발을 하기가 힘들어지자너. 기능도 계속 추가해야되고, 기획때문에 무쟈게 뜯어고치기도 해야자나 그래서 좋은 구조를 만들어야되는데, 너밖에 기대할 사람이 없거든~.. 니가 수고좀 해라잉~"

드뎌 짐을 떠맡았습니다. 이젠 대우가 쫌 좋아진것 같군요..당신은 고민을 합니다.

"이것을 어떻게 해결해야 할까.. DirectDraw를 쓴다면 각각의 유닛들이 그 유닛의 그림을 나타내는 surface들을 가지고 있을테고, 그 surface를 백서피스에 그려주는 Draw 함수를 가지고 있을 것이다. 

흠.. 여기까진 문제가 없는데.. 마법이랑, 하단의 상태창등이 문제란 말이야.. 먼저 마법을 생각해보자. 마법을 어떻게 렌더링 하도록 해야 하나? 마법을 객체로 만들어서 일괄적으로 렌더링 하도록 하면 될것 같은데.. Defensive Matrix처럼 유닛을 따라다녀야 하는 마법이 문제로군.. 흠.. 게다가 하단의 상태표시창에 특별한 표시를 해야 하는 경우도 있으니.. 이런 마법이나 상태창을 표시하는 기능을 특정한 유닛들에만 붙일 수 있어야 하는데.. Unit클래스 내에 변수를 만들고, Draw에서 변수값에 따라 if로 처리하도록 할까? 그렇게 하면 if에 의한 하드코딩이 많아져서 유연하지도않고 복잡하게 얽히게 될텐데.. 상속을 이용할까? cTank가 락다운이 걸린다면 cLockDownedTank로 바뀌도록 한다면? 여기다 디펜시브 매트릭스까지 걸린다면? cLockDowned_DMatrixed_Tank로 바뀌도록 해야 하나? 으악~!! 이렇게 하면 모든 마법-유닛 조합마다 클래스를 만들어줘야 하는데... 게다가 마법-유닛 조합이 정해져 있어서 전혀 유연하지가 않단 말이야. 흐~ 어떻게 해야되낭... 어떻게.. 어떻게..헉헉.. 어떻게..헉.. 어떻..헥.. 어떻.. 어.. 어.. 어.. ....."

이런~ 쓰러졌군요. 프로그래밍이란게 상당히 힘든거 같습니다. 

이러한 상황을 잘 해결해 줄 수 있는 것이 데코레이터 패턴입니다. 데코레이터 패턴은 링크드 리스트에서 삽입을 하는 것처럼 추가할 기능을 원래의 객체 앞에다 붙이는 것입니다.(포인터를 이용해서) 

클라이언트 코드가 탱크 클래스 인스턴스를 참조하고 있다면(포인터로 가리키고 있다면)



(클래스에 있어서 클라이언트는 그 클래스를 사용하는 부분의 코드를 말합니다. 다른 클래스이거나 main함수등이겠죠. 네트웍에서의 클라이언트가 아님)

여기에 탱크가 락다운에 걸렸다면 aTank 앞에다 aLockDown을 붙이는 것입니다. 



이 붙여진 객체(락다운)을 decorator라고 부르고, 이렇게 붙이는 것을 decorate한다고 합니다. decorator는 그것이 decorate하는 객체의 인터페이스와 통일시키기 때문에 그것의 존재는 투명하게 됩니다.(클라이언트에게는 보이지 않는다는 말임) 인터페이스를 통일시킨다는 것은 같은 인터페이스 클래스로부터 상속받는다는 것입니다. 그리고 클라이언트에서는 탱크나 락다운의 포인터가 아닌 인터페이스 클래스의 포인터를 저장하게 되기 때문에 클라이언트로서는 락다운과 탱크의 구분이 필요 없게 되는 것이지요.
인터페이스 클래스가 IUnit이라고 하면 클라이언트의 관점에서는 이렇게 보이겠죠.


클라이언트는 인터페이스 클래스인 IUnit의 포인터로 가지기 때문에 그 유닛이 무엇인지, 기능이 몇개가 덧붙여졌는지 상관할 필요가 없이 원래의 객체를 사용하듯이 사용하면 되는 것입니다. 
위의 락다운 걸린 탱크에 디펜시브 매트릭스를 걸고, 그 탱크를 플레이어가 선택했다고 하면..

+--

이렇게 되겠죠. aRenderState는 유닛의 상태를 하단의 상태바에 출력하는 기능의 클래스입니다. 
< 소스 >

class IUnit
{
public:
//데코레이터에서 사용하는 포인터
IUnit * _pNext;
IUnit() : _pNext( NULL ) {}
virtual ~IUnit() {}

virtual void Attack() = 0;
virtual void Move() = 0;
virtual void Damage(int) = 0; //공격 당했을 때 에너지 
//를 닳게함
virtual void Update() = 0;
virtual void Draw() = 0;
virtual void GetState() = 0;
};

class cTank : public IUnit
{
public:
void Attack();
void Move();
void Damage();

void Update();
void Draw();

void GetState();
};

//클래스가 스스로를 소멸시키지 못하므로 소멸을 대행해서 일괄적으로 처 
//분 하는 클래스
class cGarbages
{
//쓰레기들의 저장공간
vector< void * > _pContainer; //STL의 vector클래스
public:
cGarbages & GetInstance(); //싱글턴

void Add( void * ); //쓰레기 추가
void Disposal(); //쓰레기 모두 처분(전부 delete)
};

class cUnitDecorator : public IUnit
{
//(클라이언트 코드에서의 유닛에 대한 포인터)의 포인터
//데코레이터 리스트에서의 Head라고 할 수 있다.
IUnit ** _pPtr;
public:
cUnitDecorator( IUnit *& pNext )
: _pPtr( &pNext )
{
_pNext = pNext;
*_pPtr = this;
}

//디폴트 구현을 제공함.
virtual void Attack() { _pNext->Attack(); }
virtual void Move() { _pNext->Move(); }
virtual void Damage(int dmg) { _pNext->Damage(dmg); }
virtual void Update() { _pNext->Update(); }
virtual void Draw() { _pNext->Draw(); }

//상태 정보 리턴. sState는 유닛의 상태정보를 담는 구조체
virtual sState GetState() { return _pNext->GetState(); }

//스스로 데코레이터들의 리스트에서 삭제한다.
void SelfDestroy()
{
if( *_pPtr == this )
*_pPtr = _pNext; 
else
{
//리스트에서 이것의 앞에 있는 데코레이터를 찾는다.
IUnit * pUnit = *_pPtr; 
while( pUnit->_pNext != this )
{
pUnit = pUnit->_pNext;
}
pUnit->_pNext = _pNext;


cGarbages::GetInstance().Add( this ); //소멸
}
};

class cLockDown : public cUnitDecorator
{
int _Duration; //지속시간
LPDIRECTDRAWSURFACE _pSurface; //락다운의 그림을 저장하는 서피스
public:
cLockDown( cUnit * pNext )
: cUnitDecorator( pNext )
{}
void Attack() {} //락다운에 당하면 아무 일도 할 수 없죠. 
void Move() {} //그래서 명령을 내려도 아무일도 하지 않음 
void Draw()
{
_pNext->Draw(); //원래의 탱크를 화면에 출력한 다음
_pSurface->BltFast(); //그 위에 락다운의 그림을 출력한다.
}
void Update() 

_Duration--;
//지속시간이 지나면 자동 소멸
if( _Duration <= 0 )
SelfDestroy();

_pNext->Update();

};

//Defensive Matrix
class cDMatrix : public cUnitDecorator
{
int _Energy; //디펜시브 매트릭스의 에너지
int _Duration; //지속시간
public:
void Damage(int dmg)
{
//데미지가 디펜시브 매트릭스에 가해진다.
_Energy -= dmg;
//에너지가 0이 되면 파괴
if( _Energy <= 0 )
{
SelfDestroy(); //소멸
}
//디펜시브 매트릭스가 있는 동안은 유닛에가 데미지가 
//가지 않기 때문에 유닛의 Damage함수는 호출하지 않는
//다.
}
void Draw()
{
_pNext->Draw(); //원래의 탱크를 화면에 출력한 다음
_pSurface->BltFast(); //그 위에 디펜시브매트릭스 그림을 출력
}
void Update()
{
_Duration--;
//지속시간이 지나면 자동 소멸
if( _Duration <= 0 )
SelfDestroy();

_pNext->Update();
}
};

class cRenderState : public cUnitDecorator //상태창에 유닛 상태 출력
{
public:
cRenderState( IUnit * pUnit );
void Draw()
{
//상태를 얻어온 다음에..
sState TankState = _pNext->GetState();
//...탱크의 상태를 하단의 상태창에 출력...
}
};

//클라이언트 코드( 대충 이런 식으로 사용하게 되겠죠 )
class cGameManager 
{
IUnit * pTank;
public:
void OnClickLockDown() 
{
....
//이제 탱크가 락다운에 걸렸습니다. 
_pTank = new cLockDown( _pTank );
}
void OnClickDMatrix()
{
....
//디펜시브 매트릭스 명령이 내려지자 탱크가 마법에 걸립니다.
_pTank = new cDMatrix( _pTank );
}
void OnClickUnit( IUnit * pUnit )
{
//이제 이 탱크를 플레이어가 선택 했습니다.
pTank = new cRenderState( pTank );
}
void Update()
{
...
//이렇게 해서 탱크를 화면에 출력할 것입니다.
_pTank->Draw();
_pTank->Update();
//매 틱마다 쓰레기들을 처분합니다.
//이것땜에 속도가 저하되는게 싫으시다면 뭐~ 10틱이나 20틱, 또는
1초에
//한번씩 처분하도록 해도 되겠죠~
cGarbages::GetInstance().Disposal();
}
};

//Psynoic Storm같은 경우는 특정 유닛에게 거는 마법이 아니기 때문에 데
//코레이터를 쓰지 말고 독립된 객체로 처리해야 하겠군요.

위에서 *&(포인터 레퍼런스)를 써서 클래스를 가리키는 포인터를 조작하도록 했는데, 이렇게 쓰려면 주의해야 할 것이 있습니다. 클래스를 가리키는 포인터가 단 하나!!이어야 한다는 것.(Composite패턴을 쓰면 저절로 그렇게 되죠)

커헉! 힘들다. 위의 예제는 디자인 패턴 책하고 쪼금 다릅니다. 실시간중에 데코레이터들이 붙여졌다 떼어졌다 하기 때문에...

< 응용(적용?) >
이럴때 데코레이터를 사용합니다.

-개개의 객체에 동적이고 투명하게(다른 객체에 영향을 미치지 않고) 기능을 추가하기 위해서

-특정 기능을 독립적인 클래스로 만들기 위해서

-서브클래싱을 통해 확장을 하는 것이 좋지 않을 때, 서브클래싱을 통한 확장이 가능하지만 이럴 경우 가능한 모든 기능들의 조합을 모두 클래스로 만들어야 하기 때문에 효율적이지 못할 때

-클래스 정의가 감추어져 있거나 서브클래싱이 불가능할때(소스없이 헤더랑 라이브러리 파일들만 공개하는 경우 상속하기가 곤란하죠. 이럴때 ..)

< 결과 >
-정적인 상속보다 더 유연하다. 데코레이터는 실시간(run-time)중에 기능을 붙이고, 뗄 수 있다. 반면 상속은 각각의 기능이 추가된 서브클래스를 새로 만들어야 한다. (LockDowned_DMatrixed_Tank?) 이것은 클래스의 숫자를 증가시키고 시스템을 복잡하게 만든다. 게다가 데코레이터는 기능들을 조합해서 쓸 수 있게 해준다. 데코레이터는 같은 기능을 중첩(2개 이상)해서 붙일 수 있게 해준다. 락다운에 두 번 걸린 탱크가 있다면 aLockDown 데코레이터를 탱크에다 두 개 붙여주면 되는 것이다.

-쓰지 않는 기능을 위해 비용을 지불하지 않는다. 데코레이터를 붙이지 않은 유닛 클래스는 단지 그냥 유닛 클래스일 뿐이다. 새로운 기능을 위해 if검사 따위도 하지 않기 때문에 성능 저하가 없다. 

-확장이 아주 쉽다. 상속을 통한 확장은 슈퍼클래스의 클래스 정의에 대해 알고 있어야 한다. 때문에 슈퍼클래스가 복잡할 경우에는 확장을 하기도 복잡해지는 경향이 있다. 그러나 데코레이터는 원래의 클래스와 독립적으로 부가된 기능에만 신경쓰면 되기 때문에 원래의 클래스가 복잡하다 하더라도 간단하게 확장할 수 있다.

-많은 수의 작은 객체들.. 객체들을 생성할 때 new, delete를 사용한다면 성능 저하가 나타날 것이다. 이럴땐 Memory Pool을 만들어서 쓰면 성능 저하를 막을 수 있다.(Effective C++참조) 

이러한 작은 객체들은 서로간의 연결 관계에 따라 달라지지만 거의 비슷하게 보이게 될 것이다. 때문에 알아보기가 힘들고, 디버그도 힘들어지게 될 것이다.

-성능 저하.
위의 소스에서 cRenderState클래스를 보자. 이 클래스는 단지 Draw함수에 기능을 추가하기 위해 데코레이터를 썼는데, 나머지 함수 5개는 아무 일없이 함수를 한 번씩 더 거치게 된다. 때문에 약간의 성능 저하가 있게 된다. 성능 저하라고 해봤자 아주 약간일 뿐이지만, 속도가 중요한 부분에서는 좋지 않을 것이다. 이런 경우에는 Strategy 패턴을 쓰는 것이 좋다. 그 외에 컴포넌트 클래스가 무겁다면(변수나 함수가 많다면) 데코레이터에는 쓰지도 않는 변수들이 들어가게 되므로 그 또한 메모리 낭비나 성능 저하의 원인이 될 수 있다. 이럴 때에도 Strategy 패턴을 쓰는 것이 좋다. 
(Strategy패턴 강좌는 곧 하겠습니다.. )


p.s 끝까지 봐주셔서 캄사합니다. (꾸벅) 
다음은 Command패턴이 될듯 하군요.

Posted by Junios

2009/02/26 18:02 2009/02/26 18:02
,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/237

State 패턴

[[ State 패턴 ]]

< 의도 >
객체의 내부 상태가 바뀌었을 때 그것의 행동까지 바뀌게 하고 싶다.

< 동기 >
...스타크래프트 개발 현장...
빌로퍼가 또다시 배를 내밀며 명령을 내립니다. "지난번의 Abstract Factory 패턴으로 만든 그것에 덧붙여 유닛 클래스를 구현해야 하느니라~. 유닛은 현재 상태에 따라 행동을 하고 어떤 자극에 대해 반응을 하느니라~. Attack 명령을 내렸다면 유닛이 Attack 상태로 바뀌고 그에 맞는 행동인 '보이는 적은 뭐든지 공격한다'를 실행하게 되느니라~. Hold Position같은 경우에는 유닛이 Hold Position상태로 바뀌게 되고 '그 자리에서 이동하지 않으면서 공격 가능한 적을 공격한다'를 실행하게 된다. 이렇게 만들어야 되는데, 문제는 같은 상태에 대해 같은 행동을 하는 유닛들이 많다는 것이니라~. 예를 들어 질럿이랑 드래군등은 Stop 상태에서 공격 받았을 때 '반격한다'라는 반응을 나타내는 것으로서 동일하지만, 하이템플러 같은 경우에는 '도망간다'라는 반응을 나타내기 때문에 같지 않은 것이다. 이처럼 질럿이랑 드래군은 상태에 대한 행동이 같은데 똑같은걸 두 번 코딩할 필요는 없잖냐...~~. 이것을 니가 해결해봐라잉~. 배치기가 무섭지 않다면 농땡이를 부려도 될 것이야~~" 이렇게 명령이 떨어졌습니다.

당신은 배치기가 무서우므로 농땡이를 피울 수도 없을 것입니다. 그러나 딱히 좋은 방법이 생각나지도 않습니다. 그러자 당신은 외칩니다. 

"도와줘~!! 졸라맨!!"

그러자 졸라맨이 막대기를 휘날리며 나타납니다. 

%_TEXT_% 짜잔~~ ( ;;-_-;;; )
|\
/ \

그러나 그의 못미더운 모습을 보고 당신은 그를 무시하고(--+) 책을 뒤져보기 시작하더니 마침내 State패턴을 발견하고는 환희의 비명을 지릅니다. 꺄아~

< 소스 >
State패턴은 각각의 상태를 클래스로 만들고 인터페이스 클래스인 IState를 상속합니다. 위에서 설명한 '가상함수를 이용한 함수포인터'와 비슷합니다. 

//---------------------------------------------------------
class IState; //클래스 전방선언

class cZealot : public cUnit //cUnit은 지난 강좌에서
{ //나왔던 그거...
IState * _pState;
void ChangeState( IState * );
public:
cZealot();
~cZealot();

//이 명령들이 전달되면 유닛은 상태를 바꾸게 됩니다.
void Attack( cUnit * Target );
void Move( int x, int y ); 
void Stop();
void HoldPosition();
void Patrol( int, int, int, int );
void Update();
};
//----------------------------------------------------------
class IState {
public:
enum ID_STATE
{
STATE_ATTACK,
STATE_MOVE,
STATE_STOP,
STATE_HOLDPOSITION,
STATE_PATROL
};
private:
const ID_STATE _ID;
public:
IState( ID_STATE state ): _ID( state )
{}

ID_STATE GetID()
{
return _ID;
}
//상태에 대한 행동하는 함수.
virtual void Operate() = 0;
};

//Attack 상태. 공격 대상(target)과 행동의 주체(pUnit)을 인
//자로 받고 멤버 변수에 저장해놓습니다. 그리고 Operate함수
//에서 멤버변수에 들어간 인자값을 참조하면서 행동을 수행합니
//다.
class cState_Attack : public IState {
cUnit * _pTarget;
cUnit * _pUnit;
public:
//Attack이 끝나면 Stop상태로 바꿔야 하기 때문에 cUnit의 
//포인터를 받음
cState_Attack( cUnit * target, cUnit * pUnit )
:IState( STATE_ATTACK ), _pTarget( target ), _pUnit( pUnit )
{}
void Operate(); //각각의 상태에 대한 행동을 
//구현함
};

class cState_Move : public IState {
int _x, _y;
cUnit * _pUnit;
public:
cState_Move( int x, int y, cUnit * pUnit );
void Operate();
};

class cState_Stop : public IState {
public:
void Operate();
};

class cState_HoldPosition : public IState {
public:
void Operate();
};

class cState_Patrol : public IState {
int _x1, _y1, _x2, _y1;
public:
cState_Patrol( int x, int y, int x2, int y2 );
void Operate();
};
//---------------------------------------------------------
cZealot::cZealot()
{
//상태 객체에 넘겨줄 인자가 모두 동일하다면 new, delete 
//보다는 싱글턴을 쓰는 것이 좋을 것입니다.
_pState = new cState_Stop;
}
cZealot::~cZealot()
{
delete _pState;
}
void cZealot::ChangeState( IState * pState )
{
//상태 전이에 대한 특별한 처리가 필요하다면 여기에 코딩 
//하면 됩니다.
delete _pState;
_pState = pState;
}
void cZealot::Attack( cUnit * Target )
{
ChangeState( new cState_Attack( Target, this ) );
}
void cZealot::Move(int x, int y )
{
ChangeState( new cState_Move( x, y, this ) );
}
void cZealot::Stop()
{
ChangeState( new cState_Stop );
}
void cZealot::HoldPosition()
{
ChangeState( new cState_HoldPosition );
}
void cZealot::Patrol( int x1, int y1, int x2, int y2 )
{
ChangeState( new cState_Patrol( x1, y1, x2, y2 ) );
}
void cZealot::Update()
{
//여기서 행동이 일어나게 됩니다.
_pState->Operate();
}
//---------------------------------------------------------

위에서 상태가 바뀔때마다 new, delete를 하게 되는데 이렇게 
하면 속도가 많이 떨어지게 됩니다. 여기에다 메모리 풀(Pool)
을 만들어서 쓰면 성능을 개선시킬 수 있을 것입니다. 실험 결과 
5배정도의 속도 향상이 있는 것 같더군요. 메모리 풀은 Effecti
ve C++에 나왔으므로 그 책을 참고하시기 바랍니다. 시간나면 
제가 강좌를 쓰게 될지도 모르겠군요( 기대하진 마세요 )
//---------------------------------------------------------
< 결과 >
State패턴은 이런 장점들을 가지고 있습니다.
1. 특정 상태에 종속적인 행위를 다른 상태와 분리시킨다.
분류를 명확히 해놓는 것은 유지 보수를 위해 아주 중요한 것이죠. switch문을 통한 코딩과 State패턴을 이용한 것의 차이는 정리 안된 문서와 잘 분류되어 정리된 문서의 차이와 할 수 있겠습니다.
2. 상태의 전이를 명시적으로 만든다.
객체가 그것의 현재 상태를 단순히 내부 데이터 값으로 나타 상태의 전이가 단순히 값의 대입이기 때문에 명시적이지 못할 것입니다. 때문에 상태 전이에 대한 처리를 하기가 애매할 것입니다. if문을 남발하는 지저분한 구조가 되겠 지요. 반면에, 상태들에 대한 각각의 객체를 정의하는 것은 상태의 전이를 보다 명시적으로 만들게 됩니다. 또한 상태 객체를 이용하면 특정 상태로 전이되는 것에 대해 각 상태 클래스마다 함수를 만들어주면 되기 때문에 훨씬 유연하고 유지보수가 용이한 구조를 만들 수 있을 것입니다. 위의 소스에서는 상태의 전이에 대한 특별한 처리는 하지 않았기 때문에 필요한 분은 스스로 고안해서 쓰세요. 별로 어렵지 않을 껍니다.
3. State 객체들을 여러 클래스에서 공유할 수 있다. Attack에 대한 상태를 생각해본다면, 질럿은 공격 당했을 때 반격을 할 것이지만 하이템플러는 도망을 갈 것입니다. 때문에 서로 다른 상태 클래스를 사용하겠지만 질럿이랑 드래군은 공격을 받았을 때 똑같이 반격을 할 것이므로 Attack에 대한 상태 클래스를 공유할 수 있습니다. 

4. 더 유연하다.
switch문과 같은 방법은 정적이기 때문에 유닛의 상태에 대 한 행위를 바꿀려면 하드코딩을 해야 합니다. 그러나 State 패턴을 이용해서 다양한 상태-행위 클래스를 만들어놓고 스크립트에서 그것들을 조합해서 조립하면 유지보수, 확장등의 비용을 훨씬 줄일 수도 있고 스크립트만으로도 별 희한한 게임을 다 만들 수 있게 될 것입니다.

//---------------------------------------------------------
http://www.uml.co.kr/ 
디자인 패턴과 UML을 위한 좋은 홈페이지입니다. 한 번 들려보세요
한글로 된 강좌도 읽을 수 있고, 객체 지향에 대한 생소한 분야들을 알게 될 것입니다.


으흑~ 이번엔 데코레이터 패턴입니다. 이 패턴에 대한 예를 찾기가 힘들
더군요. 스타를 하다가 우연히 예를 발견해서 강좌를 씁니다. 제가 첨에 
패턴 배울 때 이 데코레이터 패턴이 이해하기가 가장 힘들었었는데 그 때문
인지 강좌 쓰기도 힘드는군요 -_-;;;;

출처 : 불명.

Posted by Junios

2009/02/26 18:01 2009/02/26 18:01
,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/236

Abstract Factory 패턴

[[ Abstract Factory 패턴]]

'추상적인 공장' 이라고 해석해야 하나? 암튼 그런 뜻입니다. 객체 지향에서는 abstract(추상적인)이라는 말과 concrete(구체적인)라는 말이 자주 쓰이는데, 클래스 상속에서 상대적인 개념을 나타낼 때 주로 쓰입니다. 소녀라는 클래스가 사람이라는 클래스를 상속했을 때 소녀를 concrete 클래스라고 부르고 사람을 abstract 클래스라고 부릅니다. 이것은 우리가 실 생활에서 개념을 다루는 방식과 비슷합니다. 우리가 사람이라는 개념이 소녀라는 개념보다 추상적인 개념이고, 소녀라는 개념이 사람이라는 개념보다 구체적이다라고 말하는 것과 같습니다. 또한 실 생활에서처럼 추상적인 클래스와 구체적인 클래스는 상대적인 개념이지요. 사람은 소녀에 비해 추상적인 개념이지만 동물이라는 개념에 비해서는 구체적인 개념이지요. 이처럼 사람 클래스와 동물 클래스가 있다면 사람 클래스가 구체적인 클래스(concrete)가 되고 동물 클래스가 추상적인 클래스(abstract)가 되는 것입니다.

<의도>
구체적인 클래스들을 지정하지 않고 연관되어 있는 객체들의 패밀리를 생성하는 인터페이스를 제공하고 싶다. 
책을 직역하니 대충 이런 내용이 되는군요. 이해가 안 가시리라 믿습니다. -_-;;


<동기>
스타크래프트를 개발하는 상황을 상상해 봅시다. 
빌 로퍼가 배를 내밀며 지시를 내립니다. "세 종족 모두 서로 다른 독특한 건물을 가지게 할것이고, 인터페이스는 모두 통일을 하게 만들어야 하느니라~~ 알겠느뇨~~" 
당신이 묻는다. "왜 세 종족 모두 독특한 건물을 가져야 합니까?" 
"재밌으니까!!!" 빌이 대답한다. 
당신이 또 묻는다. "그럼 왜 인터페이스는 통일시켜야만 합니까?" 
"인터페이스를 통일시키면 프로그래밍을 하기 편하니까!!"라고 빌은 또 대답한다. 
당신은 또 묻는다. "그렇지만 인터페이스를 통일시키는 작업은 쉽지 않쟎습니까?" 
그러자 빌이 인상을 찌뿌리며 말한다. "쟤네들(인공지능 프로그래머)이 몸이 쫌 약하자나~~ 니가 쫌 고생해라~~ 잉?" 
(... 이것이 게임회사의 현실입니다.. -_-;; )

당신은 이제 각기 다른 건물 구성을 가진 세 종족의 인터페이스를 통일시키지 않으면 빌의 배치기에 목숨을 잃을 판입니다. 이것에 대한 좋은 해결책이 이 패턴입니다. 이 패턴은 객체들을 생성하는 것과 사용하는 것을 분리시켜서 그 객체의 내용(구현)에 신경을 쓰지 않고 객체를 사용할 수 있도록 해줍니다. 결국에 이것은 인터페이스와 구현을 분리시키게 되는 것입니다. 

이제 빌의 배치기에 위협당하는 상황으로 돌아가봅시다. 당신이 어쩔 수 없는 상황에 부처님께 기도를 드리자 부처님이 당신의 꿈에 나타나 계시를 내려 주십니다. "너의 정성이 갸륵하여 인터페이스 클래스라는 것을 알려 주겠노라!!" 할렐루야~~

class cUnit
{
public:
virtual void Attack() = 0; //반드시 재정의 해야함
virtual void Move() = 0;
virtual void Stop() = 0;
virtual void HoldPosition() = 0;
virtual void Special1() {} //재정의 안해도 됨
virtual void Special2() {} 
};

class cZealot : public cUnit
{
public:
void Attack(); 
void Move();
void Stop();
void HoldPosition();
};

이것이 인터페이스 클래스입니다.. 먼저 순수 가상함수를 눈여겨 보세요

virtual cUnit * Create1stUnit() = 0;

이것이 순수가상함수입니다.. 뒤에 = 0; 이 붙이있는 것을 보고 의아하게 생각 하시겠지만 이것은 0을 대입한다는 뜻이 아니라 구현이 없다는 뜻입니다. 이 순수 가상함수가 하나라도 있는 클래스는 인스턴스를 생성할 수 없고 순수 가상 함수를 포함한 클래스를 상속한 클래스는 순수 가상 함수들을 모두 재정의 해야만 인스턴스를 생성할 수 있습니다. 저 늠름한 질럿 클래스를 보시면 Attack함수부터 HoldPosition까지 4개 모두를 재정의한 것을 알 수 있습니다. 때문에 Zealot 클래스는 인스턴스를 생성할 수 있지만 cUnit은 인스턴스를 생성할 수 없습니다. 만약 cZealot에서 4개의 순수 가상 함수 중 하나라도 빼먹고 재정의 하지 않았다면 컴파일러가 에러를 일으키기 때문에 cZealot의 인스턴스도 생성할 수 없게 됩니다.. 순수 가상 함수는 실제 함수가 아닌 인터페이스에 대한 약속이라고 보면 됩니다. 순수 가상 함수에 대해 설명을 하다 보면 끝이 없으니 C++ 비급을 보고 열심히 연마하시길 바랍니다.

또 알아햐 할 것은 다형성입니다. 설명을 위해 하이템플러를 추가하겠습니다.

class cHiTemplar : public cUnit
{
public:
void Attack();
void Move();
void Stop();
void HoldPosition();
void Special1();
void Special2(); //하이템플러는 마법이 세개였나?
//귀찮아서 두개만...
};

cZealot zealot;
cHiTemplar templar;

cUnit * Unit1 = &zealot;
Unit1->Attack(); //zealot.Attack()와 같은 결과

Unit1 = &templar;
Unit1->Attack(); //templar.Attack()와 같은 결과

이것이 다형성입니다. Unit1에 들어간 클래스형에 따라 그 클래스형에 맞는 함수가 호출되는 것을 알 수 있을 것입니다. 이것 역시 자세한 것은 C++비급을 참고하세요. 

이 인터페이스 클래스는 인터페이스와 구현을 확실히 분리시켜 주기 때문에 유닛을 사용에 대한 프로그래밍 할 때 유닛이 무엇인지 신경 쓸 필요도 없게 해줍니다. 또한 이것으로 인해 모든 유닛들을 큐나 리스트, 트리 등의 자료구조로 다룰 수 있게 되는 이점도 있습니다.

또 다른 이점은 사용할 때에는 인터페이스 클래스만 include 해주면 되기 때문에 컴파일 연관성을 줄여주고 컴파일을 빠르게 해준다는 것입니다. 

이제 Abstract Factory의 핵심을 설명하는 것이 남았습니다.

Abstract 클래스의 핵심은 생성하는 것과 사용하는 것의 분리와 객체 패밀리의 지정입니다.

class cPrimaryFactory //1차 생산건물에 대한
{ //인터페이스 클래스
public:
virtual cUnit * CreateUnit1() = 0;
virtual cUnit * CreateUnit2() = 0;
virtual cUnit * CreateUnit3() = 0;
virtual cUnit * CreateUnit4() = 0;

//베이스 클래스의 소멸자는 virtual이어야 합니다. 
//Effective C++참조.
virtual ~cPrimaryFactory(); 
};

class cBarrack : public cPrimaryFactory
{
public:
cUnit * CreateUnit1() { return new cMarine; }
cUnit * CreateUnit2(); { return new cFirebat; }
cUnit * CreateUnit3(); { return new cGhost; }
cUnit * CreateUnit3(); { return new cMedic; }
};

여기서 CreateUnit함수들의 구현을 단순히 [return new 유닛] 이렇게 해놨는데, 실제로는 이렇게 단순하지 않을 것입니다. 메모리 유출을 피하기 위해 먼저 Object Manager같은 클래스에 포인터를 저장하고 난 다음에 리턴하던지 하겠죠.

class cGateway : public cPrimaryFactory
{
public:
cUnit * CreateUnit1(); //질럿 생산
cUnit * CreateUnit2(); //드래군
cUnit * CreateUnit3(); //하이템플러
cUnit * CreateUnit4(); //다크템플러
};

class cHatchery : public cPrimaryFactory
{
public:
cUnit * CreateUnit1(); //저글링
cUnit * CreateUnit2(); //히드라
cUnit * CreateUnit3(); //???
cUnit * CreateUnit4(); //???
};

저그의 해처리는 특이한 구조이기 때문에 다른 패턴을 동원해야 합니다만 설명을 위해 단순화하기로 하겠습니다. 

UML도표를 그려서 설명하면 쉬운데.. 힘들군요. 뭐~ 이 강좌는 어디까지나 소개 차원에서 하는 것이므로 실제로 이 패턴들을 써서 프로그래밍을 하고 싶으신 분은 디자인 패턴 책을 반드시 사시길 바랍니다. 이미 패턴을 모두 알고 있다 해도 디자인 패턴책은 패턴 사전으로서의 가치가 크기 때문에 프로그래밍 할 때 반드시 참고하시길..

위의 PrimaryFactory를 상속한 클래스들이 Abstract Factory 들입니다. 

cPrimaryFactory * Factory1st = UI.GetUserFactory1();
//유저가 현재 선택한 종족의 
//첫번째 생산 공장에 대한 포
//인터를 얻어옵니다.

//첫번째 유닛 생산 명령이 떨어졌을 경우
Factory1st->CreateUnit1(); 
//세번째 유닛 생산 명령이 떨어졌을 경우 
Factory1st->CreateUnit3();


이렇게 사용하면 되는 것입니다. 인터페이스가 통일 되었기 때문에 공장이 어느 종족의 것인지, 유닛이 구체적으로 어떤 종류의 것인지 신경을 쓸 필요가 없게 되었습니다. 만약 이 패턴을 쓰지 않고 만든다면 if문을 이용해서 세 종족의 경우를 모두 코딩 해주어야 할 것입니다. 코딩의 비용이 3배나 늘어나게 되는 셈이지요. 게다가 구조가 바뀌었을 때 3배나 되는 코드를 수정해야 하므로 유지보수 비용도 3배가 되는 셈입니다. 그러나 실제로는 3배가 훨씬 넘는다고 할 수 있습니다. 이 패턴을 쓰면 구현과 인터페이스를 분리시키기 때문에 함수 하나의 구현이 바뀐다고해서 인터페이스를 바꿀 필요도 없을 뿐더러 대부분의 경우 바뀌는 부분이 그 함수 하나 뿐일 경우가 많습니다. 그러나 if를 이용한 구조적 프로그래밍의 경우에는 함수 구현이 조금만 바뀌어도 연쇄적으로 수정해야 할 부분이 생기는 경우가 흔합니다. 

이 패턴에서 객체의 생성과 사용을 분리시키는 이유는 인터페이스와 구현의 분리를 위해서라고 말씀 드렸습니다. 객체의 생성은 구현에 종속적일 수밖에 없는데 그 이유는 생성자에서 객체의 값을 할당하는 등의 설정을 하기 때문입니다. 객체의 생성이 단순히 new Object를 하는 것만을 의미하지는 않고, Object에 각종 설정을 하는 것을 모두 포함합니다. Bitmap클래스라면 new Bitmap을 해서 클래스를 만들고 비트맵을 로딩하고, 필요한 만큼 잘라내는 등등 사용하기 전에 세팅하는 것을 모두 포함하는 것입니다. 우리가 컴퓨터를 사용하듯이 말입니다. 컴퓨터 부품들을 새로 사서 조립을 하고 세팅을 하는 과정은 부품들에 따라 약간씩 다릅니다. 많이 다른 경우도 있지요. 그렇지만 일단 세팅을 다 끝내고 사용하는 것은 다 같습니다. 

<적용>
이럴 때 Abstract Factory 패턴을 사용하라는군요
- 시스템이 그것의 컴포넌트들이 어떻게 생성되고 조립되고 표현되는지(사용되는지)와 독립적이어야 할 때.
- 시스템이 다양한 컴포넌트의 패밀리 중 하나로 설정되어야 할 때
- 관련된 컴포넌트들의 패밀리가 함께 쓰여야 하도록 디자인 되 었고, 이런 구속을 강제해야만 할 때
- 클래스 라이브러리를 제공 하려는데 그것의 구현을 드러내지 않고 인터페이스만 드러내길 원할 때

<맺음말>
어쩌다 보니 인터페이스 클래스와 abstract Factory패턴, Factory Method패턴까지 섞여버렸습니다. 원래 디자인 패턴에서는 디자인 시의 의사소통을 위해 패턴들의 개념을 명확히 구분하는데, 이 강좌의 목적은 어디까지나 소개이므로 '디자인 패턴은 좋은 것이다!'라는 것에 초점을 맞추어 설명을 했습니다. 계속 강조하지만 디자인 패턴을 실제로 써먹으실 분들은 책을 사서 제대로 공부하시길 바랍니다. 

p.s 원래는 factory method 패턴을 따로 하려고 했는데 위에 포함이 되었으므로 따로 하지 않겠습니다. 위에서 객체의 생성을 함수에 몰아넣고 하는 것이 factory method 패턴입니다. 그리고 factory method들을 묶어놓는 것이 abstract factory 입니다.

p.s 예전에 C&C를 좋아하며 Rule을 에디트하는 에디터를 받아서 열심히 했던 적이 있었습니다. 저 에디터를 써보신 분은 알 C&C에서는 유닛들이 부품들로 이루어져 있고, 스크립 통해 부품 조합을 새로 함으로써 MOD를 만들 수 있도록 해놓았습니다. 탱크같으면 포는 레이저포, 장갑은 특수 티타늄 장갑, 이동장치는 탱크바퀴 등등 이런 식으로 객체가 나뉘어져 있습니다. 이런 것을 구현할 때에도 이 abstract fac 패턴은 아주 유용합니다. 인터페이스를 통일해 버리기 때문에 프로그래밍 부분에서는 그 부품이 어떤 것인지는 신 경을 쓸 필요가 없습니다. 단지 인터페이스를 갖고 놀면서 프로그래밍을 하면 되는 것이지요. 반면에 이 패턴을 쓰지 않으면 if문을 남발한 하드 코딩을 하게 될 것입니다. 더 좋은 방법을 생각해 낸다 하더라도 이 패턴을 이용하는 것만 많은 이점을 가지기는 힘들 겠지요. 잘 쓰시기 바랍니다.


State패턴을 하기 전에 함수 포인터의 대안으로서 가상함수를 소개해 드리겠습니다. State패턴이 사실 함수포인터랑 비슷하기 때문에...

< 가상함수를 이용한 함수포인터 >
가끔 너무 많은 if문 때문에 함수 포인터를 쓰는 경우가 있을 것입니다. 이 가상함수는 함수 포인터와 비슷하다고 할 수 있습니다. 차이점이라면 더 유연하고 직관적이며... 암튼 더 좋습니다. 

void HandleCommand( int CommandID )
{
switch( CommandID )
{
case 1: 
case 2: 
case 3:

default:
}
}

이것을 함수 포인터로 바꾸면...

void Command1() { //구현.... }
void Command2() { }
void Command3() { }

typedef void (*CommandFunc)();
CommandFunc Commands[3] = { Command1, Command2, Command3 };

void HandleCommand( int CommandID )
{
Commands[ CommandID ]();
}

대충 이렇게 되겠죠.. (맞나...?) 이것을 가상함수 버전으로 바꾸면....

class iCommand
{
public:
virtual void Operate() = 0;
};

class cCommand1 : public iCommand
{
public:
void Operate() {}
};

class cCommand2 : public iCommand
{
public:
void Operate() {}
};

class cCommand3 : public iCommand
{
public:
void Operate() {}
};

cCommand1 com1;
cCommand2 com2;
cCommand3 com3;
iCommand * Commands[3] = { &com1, &com2, &com3 };

void HandleCommand( int CommandID )
{
Commands[ CommandID ]->Operate();
}

이렇게 됩니다. 사실 위와 같은 경우는 함수포인터보다 딱히 좋다고 할 수가 없습니다. 그럼 어느 경우에 좋은가? 

- 다양한 인자를 받을 수 있다.
함수포인터는 문법 특성상 인자를 통일시켜야 하지만 가상함수를 이용한 방법은 다양한 인자를 넘겨줄 수 있습니다.
이에 대한 예제는 State 패턴에 나옵니다. 
- 둘 이상의 함수도 지정할 수 있다.
불가능하죠.
- 상속과 재정의를 이용한 확장이 용이하다.
이런 여러가지 장점이 있습니다. 클래스가 갖는 장점은 모두 가지고 있다고 보면 되겠습니다.

그리고 성능상의 장점이 있는데, switch문을 이용한 방법보다는 빠르고 함수포인터와는 비슷한 속도를 내게 될 것입니다.

Posted by Junios

2009/02/26 18:00 2009/02/26 18:00
,
Response
No Trackback , No Comment
RSS :
http://junios.net/tc/rss/response/235

Singleton 패턴

[[ Singleton 패턴 ]]

<의도>
클래스의 인스턴스를 오직 하나만 만들고 그것에 쉽게 접근을 하고 싶다.

<동기>
가끔 클래스의 인스턴스가 오직 하나만 존재해야 경우가 있을 것입니다. DirectX의 DirectDraw, DirectSound등의 컴포넌트들이 이런 경우에 해당 하겠지요. 특히나 이런 컴포넌트 들은 자주 사용하기 때문에 컴포넌트 변수를 쉽게 얻어올 수 있어야 할 것입니다. 그래서 대부분의 라이브러리를 보면 

LPDIRECTDRAW7 g_pDDraw;

이런 식으로 전역 변수를 만드는 경우가 많습니다. 전역 변수로 만들면 사용하기 편하기 때문이지요. 그러나 역시 클래스화를 하는 것이 사용하기에 더 편하므로 클래스화를 할 것입니다.

class cDirectDraw
{
...
LPDIRECTDRAW7 m_pDDraw;
public:
cDirectDraw();
~cDirectDraw();

void Create();
void Draw();
void Flip();
...
};

이런 식으로 할 것입니다. 이것도 편하긴 하지만 객체의 인스턴스를 여러개 만들려고 할 경우에 어떻게 동작할지 알 수 없습니다. 뭐~ 이 글을 보시는 분들은 DirectX를 잘 알고 계실테니 위 클래스의 인스턴스를 두개 이상 만들려고 하시는 분은 없겠지만, 여러 분이 잘 모르는 분야의 클래스를 만들 경우에는 그런 실수를 할 수도 있습니다. 

또는 프로젝트가 커지고 복잡해져도 그런 실수를 하기 쉽겠지요. 클래스 옆에다 주석으로 '인스턴스를 두 개 이상 만들면 안 됨!!' 이라고 써놓는다고 해도 그런 실수를 할 가능성은 여전히 존재합니다. 게다가 기억하고 있어야 할 주의사항이 많다면 머리속을 혼란하게 만들죠. 

이것들에 대한 좋은 해결책이 바로 이 싱글턴 패턴입니다. 이 패턴은 전역변수만큼 사용하기 쉽게 해주고, 절대 하나 이상의 인스턴스를 만들지 못하도록 막아줍니다. 
<소스>

class cDirectDraw
{
...
LPDIRECTDRAW7 m_pDDraw;
public:
//인스턴스를 얻어오는 함수
static cDirectDraw & GetInstance(); 

void Create();
void Draw();
void Flip();

~cDirectDraw();
...
protected:
//인스턴스 생성을 막는다
cDirectDraw(); 
};

cDirectDraw & cDirectDraw::GetInstance()
{
//static이므로 오직 하나만 생성
static cDirectDraw DDraw; 
return DDraw;
}


<사용예>

cDirectDraw & DDraw = cDirectDraw::GetInstance();
DDraw.Draw();
DDraw.Flip(); 

//또는
cDirectDraw::GetInstance().Draw(); 
//이런 식으로 사용해도 됩니다.

여기서 눈여겨 봐야 할 부분은 생성자와 GetInstance함수 입니다.
생성자는 외부에서 cDirectDraw의 인스턴스를 만들지 못하게 하기 위해서 protected에넣었습니다. 만약 cDirectDraw의 인스턴스를 만들려고 하면 컴파일 에러를 일으킬 것입니다. 그리고 GetInstance()함수는 아무때나 사용할 수 있도록 static 함수로 만들었습니다. 

GetInstance()함수의 정의 부분에서는 cDirectDraw의 인스턴스를 static으로 만들었기 때문에 함수가 계속 호출되도 오직 하나의 인스턴스만 만들게 됩니다. 게다가 함수가 처음 호출되는 때에 static 변수를 생성하기 때문에 GetInstance()함수를 호출하지 않으면 cDirectDraw클래스 인스턴스도 만들지 않습니다. 



p.s 사실 위에서 구현한 방식은 상속을 할 경우 문제가 발생합니다. 상속을 할 수 있게 하기 위한 좀 더 복잡한 구현이 디자인 패턴 책에 있으니 원하시는 분은 그것을 참고하세요. 아니면 메일을 주세요~. 그러면 따로 강좌를 드리겠습니다. 첫 강좌를 쓰고 나니 왠지 굉장히 귀찮아 지는군요. 역시 게으른 자는 이래서 안된다니까.. >.< 
그래서 '괜히 패턴을 8개나 소개한다고 했구나..' 하고 후회하고 있던 중에 이 강좌를 봐주시는 분이 있다라는 것을 알고 좀 부지런 해지기로 했습니다. 

암튼 읽어주시는 분들께는 감솨를 드립니다. ㅠ_ㅠ

그리고 깜빡하고 말씀 안드린 것이 있는데, 디자인 패턴은 언어에 독립적입니다. 여기서 C++을 써서 구현을 했지만 다른 모든 언어에서 구현이 가능하다고 합니다. 심지어 기존의 C로도 구현이 가능하다고 합니다. 저는 C++밖에 모르기 때문에 '어떻게 다른 언어로 구현하느냐'라고 묻지는 마세요.

출처: 불명 -_-; 

Posted by Junios

2009/02/26 17:58 2009/02/26 17:58
,
Response
4 Trackbacks , No Comment
RSS :
http://junios.net/tc/rss/response/234


블로그 이미지

Junios World

- Junios

Archives

Authors

  1. Junios

Calendar

«   2010/09   »
      1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30    

Site Stats

Total hits:
32813
Today:
23
Yesterday:
139