가끔 아니 어쩌면 자주 3D 공간안에서 물체의 위치를 지정할때 이 물체가 저쪽에 닿는 면에 딱 위치하고 싶은데... 할때가 있죠?
예를 들면 나무나 돌덩이 이런걸 랜덤하게 막 생성한 다음 굴곡진 지형에 모두 딱 안착시키고 싶다거나요.
또는 어떤 물체를 기준으로 어떤 방향으로 뻗어 나갈때 무엇인가 닿는 물체가 있는지 검사하고 싶을때도 있지요.
이때 사용되는 개체가 바로 Ray 입니다.
실제로 Ray를 알기 전과 알고난 뒤는 정말 다른 세계라고 할수 있겠습니다.
자 오늘은 Ray 에 대하여 알아보도록 하겠습니다.
3D 는 정말 놀라울 정도로 기하학적인 수학의 결정체라 할 수 있겠습니다. 이를 구현하기 위하여 정말 얼마만큼의 수학적 노력이 있었는지 상상이 안될 정도 입니다.
오늘은 그 결정체중의 하나인 Ray에 대하여 알아볼텐데요. 3DS MAX 에서 Ray 는 두가지 명령어를 통해 사용됩니다.
intersectRay 와 intersectRayEx, intersectRayScene 이렇게 세가지 입니다. 사용하는 목적에 따라 구분하여 사용하시면 되는데요. 각각은 아래와 같습니다.
- intersectRay : Ray 와 대상물체를 지정한 뒤 부딧히는 위치를 알아낸다.
- intersectRayEx : 위와 동일하며 부딪힌 Face 정보와 부딪힌 면의 normal 정보를 돌려준다
- intersectRayScene : 대상 개체를 지정하지 않고 Scene 내의 어떤 물체든 부딪힌 정보를 돌려준다. (느림)
사용하는 방법은 간단한데요, 먼저 Ray 를 생성해주고 필요하면 Ray의 출발 위치(pos)와 방향(dir)을 변경할 수 있습니다.
myRay = ray [0,0,0] [0,0,-1]
ray 를 생성하는 구문 인데요, [0,0,0] 위치를 출발점으로 하고 [0,0,-1] 을 방향으로 하는 ray 가 만들어 지게 됩니다. [0,0,-1] 이라 하면 z 축을 기준으로 아래쪽을 바라보는 방향이라고 보면 되겠죠?
이렇게 만들어진 Ray를 위의 명령어와 함께 사용해볼까요?
먼저 위와 같은 Scene 을 만들어서 테스트 해보려 합니다. 노란색 공이 지면에 부딪히는 위치로 이동 시킬거에요. 중간에 네모 박스가 있지만 네모박스는 무시하고 지면에 위치시켜야 한다면 아래와 같이 intersectray 를 이용하여 위치시킬 수 있습니다.
-- ray 를 생성해 준다
myRay = ray [0,0,0] [0,0,-1]
-- 이동시킬 물체를 지정하자
obj = $Sphere001
-- ray 의 출발 위치를 물체의 중심으로 설정해보자
myRay.pos = obj.pos
-- 위치 검출에 사용될 지형을 지정해보자
tern = $terrain_01
-- intersectray 를 이용하여 지형과 부딪히는 위치를 찾아내자
interResult = intersectray tern myRay
-- 어떤 결과가 나왔는지 print 를 이용해서 살펴보자
print(interResult)
-- 부딪힌 위치에 오브젝트를 위치시키자
obj.pos = interResult.pos
요렇게 하면 어떻게 되냐면요
요렇게 노란 공이 지면에 달라 붙게 됩니다. 코드에 주석을 달아 놓았으니 따로 설명은 안드려도 될 것 같은데요. 한가지만 설명해 드리자면 intersectray의 결과값을 보면 (ray [-1.62331,11.1008,1.22501] [-0.407511,-0.107746,0.906822]) 요런 식으로 ray 의 형식으로 결과가 나옵니다. ray 는 pos 와 dir 의 속성이 있지 않습니까?? 여기서 결과값의 pos 정보가 바로 부딧힌 면의 위치 정보가 됩니다. 그 뒤에 나오는 [-0.407511,-0.107746,0.906822] 는 부딪힌 면의 normal 값으로 만약 공이 부딪힌 다음 튕겨나간다거나 하는 다음 동작이 필요하다면 반드시 필요한 정보이지요.
어쨌든 intersectray 만 보자면 부딪힌 위치와 각도를 ray 의 형태로 돌려준다는 것을 알 수 있었습니다. 특징이라 하면 , 특정 개체를 대상으로 하기 때문에 속도가 매우 빠릅니다. 사실상 성능을 고려하지 않아도 되는 함수죠.
그런데 만약 부딪힌 면이 어디인지 알아야 하고 부딪힌 개체에도 뭔가 추가적인 액션이 필요할 수 있겠습니다. 사람일은 모르는거자나요? 누군가는 저 지형개체에 어떤 face에 물체가 부딪혔는지를 알아야 할수도 있지 않겠습니까? 이 때 intersectrayex 라는 함수를 이용하게 됩니다.
이번에는 intersectray 대신에 intersectrayex 를 사용해 볼 겁니다. 사용방법은 동일하고요. intersectrayEx [node] [ray] 와 같이 사용해 주면 됩니다.
-- intersectray 를 이용하여 지형과 부딪히는 위치를 찾아내자
interResult = intersectrayex tern myRay
-- 어떤 결과가 나왔는지 print 를 이용해서 살펴보자
print(interResult)
-- 결과는?
#((ray [-1.62331,11.1008,1.22501] [-0.382998,-0.128105,0.914823]), 240, [0.383149,0.282177,0.334673])
아까와는 결과가 조금 다르죠? #(oo,oo,oo) 형태로 나온걸 보면 배열로 결과가 나온걸 알 수 있습니다. 총 3개의 리턴값이 나왔는데요.
- (ray [-1.62331,11.1008,1.22501] [-0.382998,-0.128105,0.914823])
- 240
- [0.383149,0.282177,0.334673]
intersectray 와 마찬가지로 첫번째 결과에는 부딪힌 면의 위치, 방향을 담는 ray 정보가, 두번째에는 어떤 숫자가 나왔고요, 세번째에는 point3 형식으로 좌표가 나왔는데, 딱보니 위에 ray 에 부딪힌 면의 방향이라는 것을 알 수 있겠습니다. 두번째 숫자는 뭐냐고요?
바로 부딪힌 mesh 의 face 인덱스 입니다. 240 번이죠? 바로 저부분에 부딪힌 것 입니다. (이번에는 이동은 하지 않았기 때문에..) 만약 동일하게 이동해야 한다면 배열 안의 개체에서 위치 정보를 뽑아내야 하므로 interResult[1].pos 와 같은 방법으로 부딪힌 면의 위치를 가져오면 됩니다.
이런 기능을 이용해서 부딪힌 면의 색상을 바꾸어 준다거나 해당 face를 삭제하는 등의 기능을 넣을 수 있겠지요? 뭐 레이저총에 맞은 face 가 폭파되며 사라지는 등의... 네.. 그럴때 꼭 필요한 훌륭한 명령어 입니다.
참고로 intersectray 나 intersectrayex 는 mesh 오브젝트에서 정확한 정보를 받아 올 수 있습니다. 가끔 기본 primatine 에 modifier 로 edit_poly 같은것을 씌운 경우에는 동작하지 않을 수 있으니 만약 결과값이 undefined 와 같은 값이 나온다면 검출에 사용된 모델링을 mesh 로 변경한 뒤 (convertTomesh obj) 검사를 진행하시면 됩니다.
끝으로 intersectRayScene 은 위와는 다르게 Ray 가 어떤 개체에 부딪히는지 알수 없을 때 사용합니다.
모두 동일하고 해당 명령만 intersectRayScene 으로 변경해서 돌려볼까요?
interResult = intersectrayScene myRay
요렇게 말이죠. 이번에는 대상을 특정하지 않기 때문에 함수 뒤에 인자가 ray 개체 하나만 들어갔습니다.
결과를 볼까요?
#(#($Editable_Mesh:terrain_01 @ [-4.616531,-2.612816,0.000000], (ray [-1.62331,11.1008,1.22501] [-0.382998,-0.128105,0.914823])), #($Sphere:Sphere001 @ [-1.623309,11.100846,65.260162], (ray [-1.62331,11.1008,61.9389] [0,0,-1])), #($Box:Box001 @ [15.646946,18.567043,19.666662], (ray [-1.62331,11.1008,26.4499] [0,0,1])))
뭔가 무시무시하게 길게 나왔죠?
이녀석도 역시 배열 개체이므로 하나씩 보면
#($Editable_Mesh:terrain_01 @ [-4.616531,-2.612816,0.000000], (ray [-1.62331,11.1008,1.22501] [-0.382998,-0.128105,0.914823]))
#($Sphere:Sphere001 @ [-1.623309,11.100846,65.260162], (ray [-1.62331,11.1008,61.9389] [0,0,-1]))
#($Box:Box001 @ [15.646946,18.567043,19.666662], (ray [-1.62331,11.1008,26.4499] [0,0,1]))
요렇게 3개의 개체와 부딪혔다는 정보를 돌려준 것을 알 수 있습니다. 심지어는 본인(sphere) 에도 부딪힌 정보를 돌려 받았다는 것을 알 수 있네요 ㄷㄷㄷ. (저걸 원한건 아닌데) 부딪힌 정보를 보면 역시 내부 정보 역시 배열인데요, 첫번째는 부딪힌 대상 개체, 두번째는 부딪힌 위치정보 (ray 속성) 라는걸 알 수 있습니다.
만약 개체에 어떤 특정할만한 속성이 있다면 해당 개체에 물체를 위치 시킬 수 있습니다.
-- ray 를 생성해 준다
myRay = ray [0,0,0] [0,0,-1]
-- 이동시킬 물체를 지정하자
obj = $Sphere001
-- ray 의 출발 위치를 물체의 중심으로 설정해보자
myRay.pos = obj.pos
-- 위치 검출에 사용될 지형을 지정해보자
tern = $terrain_01
-- intersectray 를 이용하여 지형과 부딪히는 위치를 찾아내자
interResult = intersectrayScene myRay
-- 어떤 결과가 나왔는지 print 를 이용해서 살펴보자
print(interResult)
for i = 1 to interResult.count do
(
testObj = interResult[i][1]
if (findString testObj.name "Box") != undefined then
(
obj.pos = interResult[i][2].pos
)
)
요렇게요.
코드에서는 Box 라는 이름을 포함하는 개체를 만나면 해당 개체에 위치하도록 하는 코드 입니다.
이게 사실 한두개일 때는 간단한 이야기 인데 지면에 위치 시켜야 하는 개체가 수십개 ~ 수백개가 되면 일이 커집니다. 그럴때는 스크립트가 필요한 시점인거죠.
예를 들면 이렇게요..
모두 sphere 라는 이름을 갖고 있기때문에 간단하게 처리할 수 있겠네요. 지면에 닿는 개체는 지면에 닿도록 하고 box 에 닿는 개체는 box 에 위치시키도록 하겠습니다.
sphere 의 중심을 기준으로 하다보니 약간 파뭍히는 대상이 있었는데요, sphere 의 반지름 만큼 위쪽으로 위치하도록 코드를 짜 보겠습니다.
-- ray 를 생성해 준다
myRay = ray [0,0,0] [0,0,-1]
-- 이름이 sphere 로 시작되는 모든 개체를 변수 objs 에 담자.
objs = execute("$Sphere*")
for obj in objs do
(
-- 각 sphere 의 중심점으로 ray 의 위치를 지정함
myRay.pos = obj.pos
-- intersectray 를 이용하여 지형과 부딪히는 위치를 찾아내자
interResult = intersectrayScene myRay
for i = 1 to interResult.count do
(
testObj = interResult[i][1]
if (findString testObj.name "Box") != undefined then
(
obj.pos = interResult[i][2].pos
-- 파묻히지 않도록 반지름 만큼 위로 올림
obj.pos.z += obj.radius
)
else if (findString testObj.name "terrain") != undefined then
(
--- 부딪힌 정보가 terrain 일때 해당 위치로 이동 시킴
obj.pos = interResult[i][2].pos
-- 파묻히지 않도록 반지름 만큼 위로 올림
obj.pos.z += obj.radius
)
)
)
뭐 별거 없죠? 위에서 설명한 것을 반복문만 추가했을 뿐 입니다.
결과는요?
요렇게 예쁘게 안착된 것을 확인 할 수 있습니다.
물론 저는 본 기능을 설명 드리기 위해 위와 같은 예제를 들었지만요. 실제 제가 사용하는 함수에는 몇초만에 수천번 이상 위치를 검출하며 ray 가 사용되는 케이스가 허다분 합니다.
저는 주로 자동으로 모델링을 할때 모델링의 위치, 크기, 배치, 부딪힘, 확장 범위 등을 검사할때 사용하는데요, 이런 검사들을 통해 사람이 손을 데지 않고도 아주 정교한 모델링을 할 수 있는 근간이 됩니다.
참고로 intersectRayScene 의 경우 scene 의 모든 개체에 대하여 검사가 이루어지기 때문에 Scene 내에 아주 많은(수만개 이상의) node 들이 자리잡고 있는 경우에는 속도가 매우 느려질 수 있습니다. 그래서 시작 위치를 기준으로 반경내에 들어오는 대상을 먼저 골라낸 다음 속도가 빠른 intersectRay 를 이용하여 각각 부딪힌 위치를 검출하는 방식으로 사용하고 있습니다. 프로그래밍이라는게 그렇자나요. ㅎ 높은 성능과 효율성을 위해 다양하고 창의적인 방법으로 접근해 가는게 바로 프로그래밍의 묘미이지요.
오늘은 이만 마칩니다. 내용이 길어기니 힘드네요 ㅋ
2021.03.24 - [DEV/MAX SCRIPT] - 3DS MAX 스크립트 레퍼런스 (헬프파일) 다운 받기
2021.02.24 - [DEV/MAX SCRIPT] - 3da max script 점과 점 사이의 거리 구하기
2020.04.29 - [DEV/MAX SCRIPT] - 3DS MAX 스크립트로 개체 선택하기
'DEV > MAX SCRIPT' 카테고리의 다른 글
[3ds max script] 스크립트의 계층 구조에 대하여 (2) | 2022.11.16 |
---|---|
3DS MAX 스크립트 레퍼런스 (헬프파일) 다운 받기 (4) | 2021.03.24 |
[3DS MAXscript] 스크립트 에디터 한글이 ??? 로 나오는 문제 해결 방법 (2) | 2021.03.03 |
[3ds max script] vector 에 대하여 (2) | 2021.02.26 |
[3ds max script] 카메라 생성, 이동 시키기 (14) | 2021.02.26 |