Posted by Junios
Posted by Junios
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
이 문서는 제가 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
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
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();
}
}
Posted by Junios
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같은 경우는 특정 유닛에게 거는 마법이 아니기 때문에 데
//코레이터를 쓰지 말고 독립된 객체로 처리해야 하겠군요.
Posted by Junios
//---------------------------------------------------------
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();
}
//---------------------------------------------------------
Posted by Junios
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;
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()와 같은 결과
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; }
};
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(); //???
};
cPrimaryFactory * Factory1st = UI.GetUserFactory1();
//유저가 현재 선택한 종족의
//첫번째 생산 공장에 대한 포
//인터를 얻어옵니다.
//첫번째 유닛 생산 명령이 떨어졌을 경우
Factory1st->CreateUnit1();
//세번째 유닛 생산 명령이 떨어졌을 경우
Factory1st->CreateUnit3();
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();
}
Posted by Junios
LPDIRECTDRAW7 g_pDDraw;
class cDirectDraw
{
...
LPDIRECTDRAW7 m_pDDraw;
public:
cDirectDraw();
~cDirectDraw();
void Create();
void Draw();
void Flip();
...
};
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();
//이런 식으로 사용해도 됩니다.
Posted by Junios