max sdk 관련 자료
반응형
이 문서는 제가 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
한것입니다. 저만 알아볼 수 있도록 되어 있는 부분이 많습니다. 시간날 때 다시 정리
하겠습니다.
--------------------
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
'유용한 것들_etc' 카테고리의 다른 글
메모관리 프로그램 (0) | 2009.12.02 |
---|---|
유니코드 표 (0) | 2009.11.25 |
win api + dialog (0) | 2009.11.19 |
언리얼의 한글화라.... (0) | 2009.11.07 |
Uninstall 아이콘만들기 (0) | 2009.09.14 |
TAGS.