* 이전 블로그 백업글 : 이 게시물은 2008~2013년 사이에 작성된 것으로, 2024년 현재의 환경과는 맞지 않을 수 있습니다.
OGRE Add-On Project의 일부.
Ruby Script로 제작.
위키 소개: http://www.ogre3d.org/wiki/index.php/Sketchup_Exporter
공식홈 소개: http://www.ogre3d.org/index.php?option=com_content&task=view&id=17&Itemid=70
소스는 첨부파일에 포함. 오픈소스이므로 별 문제는 없을 듯.
Export()
, exportDialog()
, grabDialogData()
, saveConfig()
는 따로 분석할만한 내용은 없다.
핵심은 collectMaterials()
함수. 여기서 정점, 재질 데이터 전부를 뽑고 writeMaterials()
, exportFaces()
에서 데이터를 XML 형태로 써넣는다.
일단 collectMaterials(matlist, ents, trans, inherited_mat, root)
함수 분석.
인자 중 matlist는 메테리얼 리스트이다.
하지만 이 코드에서는 matlist 내에 메테리얼 정보만이 아니라 면(face)과 , 변환(transform) 정보까지 포함시켜서 모든 데이터를 여기서 뽑아낸다.- matlist는 배열이며, 배열의 각 요소는 다음과 같은 구조로 이루어진다.
[ handle, [ [face,trans,frontface, inherited_mat] ] ]
루비를 제대로 공부한 적이 없어서 분석이라기보다는 거의 찍기 수준이지만, 대강 C의 구조체 배열처럼 생각하면 될 듯. 제대로 루비 공부한 사람이 보면 비웃을 것 같다 -_-;- 일단 handle와 배열 하나 해서 두 개의 인자를 가지는 구조체(C식으로 설명하자면)의 배열이 된다.
- 두 번째 인자인 배열은 face,trans,frontface, inherited_mat의 4개 요소를 가지는 구조체의 배열이다. ([]가 두 겹으로 되어 있다는 데 유의해야 할 듯)
사실 루비를 제대로 공부한 게 아니라 거의 찍기 수준.
ents는 엔터티 리스트.
trans는 변환값으로, 초기에는 변환이 없는 행렬이 들어간다.
inherited_mat는 계층관계에서 자신의 부모가 가진 메테리얼이다. SDK쪽에서 적어놨듯이 스케치업은 면 단위가 아니라 컴포넌트 단위로 재질을 입힐 수 있고, 이 경우 각 면은 자신이 속한 컴포넌트의 재질 정보를 계승받는다.
- root는 아직 뭔지 정확히는 모르겠지만, 인스턴스일 경우 false로 처리하고, 내부적으로는 face 데이터를 뽑을지 말지를 결정하는 데 사용된다.
다음은 함수 전체 소스.
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
def collectMaterials(matlist, ents, trans, inherited_mat, root)
for e in ents # 각각의 엔터티별로 루프를 돈다.
case e
when Sketchup::Face # 엔터티가 면이라면
if (not root) or OgreConfig.exportRootFaces # root 인자와 설정값에 따라
if OgreConfig.exportFrontFaces # 전면의 정점을 뽑을 것인지
if e.material # 재질 정보가 있는 경우.
mat = e.material
handle = @@tw.load(e,true)
# 위에 설명한 matlist의 구조에서 사용되는 handle값. collectFace() 참조.
else
if inherited_mat # 상위 구조에서 계승받은 메테리얼이 있을 경우
mat = inherited_mat[0]
handle = @@tw.load(inherited_mat[1],true)
else # 아무것도 없을 경우
mat = nil
handle = 0
end
end
m = matlist[mat]
collectFace(m, e, trans, handle, true, if mat then nil else inherited_mat end)
# 이 함수는 나중에 설명
end
if OgreConfig.exportBackFaces # 후면의 정보도 전면과 방식은 똑같다.
if e.back_material
mat = e.back_material
handle = @@tw.load(e,false)
else
if inherited_mat
mat = inherited_mat[0]
handle = @@tw.load(inherited_mat[1],false)
else
mat = nil
handle = 0
end
end
m = matlist[mat]
collectFace(m, e, trans, handle, false, if mat then nil else inherited_mat end)
end
end
when Sketchup::Group
# 그룹일 경우 재귀호출을 통해서 그룹에 속한 각각의 엔터티에 대해 함수를 수행한다.
# trans에 현재 그룹의 위치 정보가 더해진다.
# 현재 그룹에 메테리얼이 있으면 inherited_mat에 추가한다. 메테리얼, 엔터티, 변환 정보가 포함되는 듯.
collectMaterials(matlist, e.entities, trans*e.transformation, if e.material then [e.material,e,e.transformation] else inherited_mat end, root)
when Sketchup::ComponentInstance
# 인스턴스일 경우 해당 컴포넌트의 원본(definition)에 속한 각각의 엔터티에 대해 함수 재귀호출. 그 외의 것은 그룹의 경우와 같다.
collectMaterials(matlist, e.definition.entities, trans*e.transformation, if e.material then [e.material,e,e.transformation] else inherited_mat end, false)
end
end
end
- 함수 인자.
m : 메테리얼 정보. 정확히는 [핸들 – [면, 변환, 전/후면, 상속된 메테리얼]의 배열]의 배열.
face : 면(face) 정보
trans: 변환 행렬
frontface : 전/후면 여부를 나타내는 Boolean값.
inherited_mat : 상속된 메테리얼 정보. 정확히는 [메테리얼, 엔터티, 변환 행렬]의 구조체.
1
2
3
4
5
6
7
8
9
10
11
12
13
def collectFace(m, face, trans, handle, frontface, inherited_mat)
index = nil
# times는 클래스의 값만큼 반복하는 반복자의 일종으로 추정(each처럼)
# i에 대해 루프를 돈다고 생각하면 될 듯 (감으로 찍기, 이미 프로그램이 아니다 -_-)
(m.size).times {|i| if m[i][0]==handle then index = i end}
# index가 nil이 아니라는 것은 인자로 받은 handle에 해당하는
# 메테리얼이 이미 m에 있다는 뜻이므로, 값만 채워넣는다.
if index
m[index][1].push([face,trans,frontface, inherited_mat])
else # 핸들값이 없을 경우 전체 구조체를 m에 추가한다.
m.push([handle,[[face,trans,frontface, inherited_mat]]])
end
end
개인적으로, 대체 왜 이런 구조를 쓰는지 이해할 수가 없다. 메테리얼 리스트 따로 모으고 face 데이터는 따로 모으면 어디 덧나냐? 왜 이걸 메테리얼 쪽에 다 몰아넣으려고 하는건지, 게다가 엔터티 ID 놔두고 왜 텍스쳐 핸들값을 인덱스로 쓰는데? -_-;
아무래도 루비로 익스포터를 만들게 되면 아예 새로 짜야 할 듯.