머라 할말이 없는 이시대에 ..
짜증난다....~~
Posted by Junios
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