본문 바로가기

Houdini/Houdini1_Rigidbody

RIGID BODY_14 접한면에 자동으로 컨스트레인트 구하기 - Ep14_Part03. Constraint System(자동화+RnD)


우리가 이제 고민해줘야할 것은 '어떻게 시스템화해서 constraint를 만들어줄 것인가?' 이다.

 

목표는 물체를 통째로 넣어줬을 때, 원하는 위치에 포인트가 생성이 되고, constraint까지 만들어지는 것이다. 정답은 하나만 있는 것이 아니다.

 

핵심은 노드가 제공하는 결과를 어떻게 영리하게 활용하느냐 이다.

 


아이디어

1) 물체와 물체가 닿아있는지, 닿아있지 않은지 판별을 해준다.

  • boolean을 사용해서 operation > intersect(교집합)로 두 물체가 겹치는 아닌지를 판단해줄 수 있을듯 하다.

boolean을 사용하는 예시를 만들어보자.

platonic solid > Octahedron을 만들어주고 add 로 점을 만들어준 뒤, 몇개를 복제해준다.

그리고 그 중 두개를 떼어내고, boolean > intersect로 겹치는 부분을 구해준다.

겹치는 부분을 기준으로, 두 물체 사이의 연결관계를 만들어주고 싶다면, boolean으로 얻은 결과의 중심을 포인트로 구해주고, 그 포인트를 각 물체를 대변하는 포인트로 활용해서 constraint를 세팅해주면 될 것이다.

핵심은 boolean을 활용해서 구해준 결과의 중심을 활용해서 포인트를 만들어준 것이다.


다시 의자로 돌아와서 생각해보자.

위의 이미지의 다리와 시트는 현재 맞닿아있기 때문에 boolean > intersect를 적용하면 겹치는 부분이 남게 될 것이다.

 

... 라고 생각했지만, 결과는 아무것도 나오지 않았다.

이 결과의 의미는, 어떠한 경우는 boolean으로 겹치는 구간을 기준으로 constraint에 필요한 점을 찾는 것이 가능한 순간이 있고, 어떠한 경우는 방금처럼, 이미 겹치는 구간이 존재한다는 것을 알고는 있지만, boolean > intersect로 해결이 안되는 순간도 존재한다는 것이다. 

 

그렇다면, 생각을 바꿔서, intersect 말고 union을 사용해보자.

뚫려있다.

union은 무슨 의미가 있을까? 적어도 이것을 활용해서 bound로 중심점을 찾아주는 것은 의미가 없어보인다.

 

union이 유의미해지는 부분은 위의 이미지처럼 '뚫린 부분'이다.

만일 위의 이미지처럼 노드가 구성되어있을 때, unpacking된 두 물체가 맞닿아있다면, merge3의 결과와 boolean1의 결과는 다를 것이다.

다르다.

primitive가 하나 줄어든 결과가 나오는 이유는, 다리의 윗면이 union에 의해 사라지면서 줄어들게 되는 것이다.


다시 아까의 예제로 돌아가서 union을 적용했을 때의 결과를 비교해보자.

두 물체가 '겹치지 않는 물체'라면, merge와 boolean > union은 차이가 있을까?

맞닿을 일조차 없고, 겹치지 않는 떨어져있는 물체들이라면, merge의 결과와 boolean > union의 결과는 차이가 없을 것이다.

이처럼 떨어져있는 물체들이라면,
boolean > union과 merge의 결과는 같다.

여기에서 생각해볼 수 있는 것은, 조금이라도 닿아있는 물체들이라면, merge와 boolean의 데이터는 차이가 발생한다는 점이다.

 

이것을 이용해보도록 하자.


만약 물체들이 닿아있다면, 닿아있는 부분에 대해서 constraint에 사용할 포인트를 찾아내도록 하면 되고, 물체들이 떨어져있다면, 이렇다 할 포인트를 찾을 필요가 없을 것이다. 조건문이다.

 

그렇다면 이 판별을 위해 어떻게 식을 구성해야할까?

 

물체가 닿아있다면, 포인트의 개수, 혹은 primitive의 개수가 차이가 나게 될 것이다. 이것을 이용해주도록 하자.

 

포인트의 개수와 primitive의 개수를 활용할 수 있도록 attribute로 정보를 뽑아낸다.

참고로, 물체가 맞닿아있다고 해서 포인트의 개수와 primitive의 개수가 둘 다 변동이 생기는 것은 아니다. 둘 중 하나라도 변동이 발생한다면, 물체는 닿는 부분이 존재한다고 봐야할 것이다.

 

switch 노드를 활용해서 if 조건문을 만들어주기 위한 세팅을 했다.

이렇게 세팅하게 되면, merge와 boolean의 포인트 개수 혹은 primitive 개수가 다를 경우, constraint 세팅을 위한 포인트를 만들어주도록 하면 된다. null 노드를 달아서, 위의 판별이 거짓일 경우(서로 닿는 부분이 없어서 포인트 개수, primitive 개수가 다 같을 경우) constraint를 위해 중심을 찾고 포인트를 만드는 작업은 스킵하게 될 것이다.

 

 

이제 겹치는 부분을 어떻게 찾아줄 수 있을까?

만약 각각의 primitive(혹은 point)가 고유한 이름을 가지고 있다면, merge에 있는 primitive(혹은 point)가 boolean에는 없을 것이다. 

맨 뒤에 붙는 andpoints 부분은 primitive를 지우면서 point도 지우길 원한다면 1을 적어주면 된다.

 

이렇게 찾아준 primitive의 중심을 찾아서 constraint에 사용할 수 있다.


이제 해야할 일은, 각각의 조각들이 자신을 제외한 모든 조각과 비교를 해서 닿는 면이 있는지 없는지 확인하는 시스템을 만들어줘야한다.

 

for each로 메인이 되는 조각을 선택하고, 나머지 조각들을 선택 비교하는 작업을 또 다른 for each로 진행하면 될 것인데, 주의할 것은 메인이 되는 조각을 선택비교하는 조각들 사이에서 제외해주는 것을 자동적으로 되도록 해주는 것일 것이다.

그리고 위에서 작업한 내용을 for each에 연결해주면...

접하는 부분에 대해 constraint에서 활용할 포인트가 만들어졌다.

 

다른 물체에도 제대로 적용이 되는지 확인해보자.

연결했지만, 포인트가 만들어지지 않았다. 그 이유로는 저렇게 겹쳐있는 케이스에서는 merge와 boolean의 primitive 수가 같아서 전부 지워버리는 상황이 발생할 수 있기 때문이다.

판별에서 primitive 수가 같다.

조건을 좀 더 세분화해주고, 이런 경우 어떻게 해줘야하는지 작업해줘야할 필요가 있다.

 

boolean의 기능이 필요한데, boolean을 통해 만들어지게 되는 edge를 그룹화해줄 수 있다.

boolean > abseams 그룹을 활용하여 중심을 구할 수 있었다.
바꿔준 결과는 의자에도 잘 적용이 되었다.


한가지만 더 확인해보자.

voronoi fracture가 적용된 물체에는 잘 작동할까?

어딘가 만들어지지 않은 구간도 있고 그렇다. 이것은 왜 그럴까?


Voronoi Fracture

포인트를 중심으로 물체를 쪼개는 노드

 

voronoi fracture의 결과로 나온 것 중 두개의 조각을 떼어내고,
boolean > union을 해줬다.(포인트 넘버를 보면 무언가 변화는 있다.)
겹친 부분이 제대로 뚫리지 않고, 면이 겹쳐져있다. 제대로 boolean이 적용되지 않은 것이다.

voronoi fracture는 이미 쪼개질 때 안쪽 면에 대해 그룹이 지정되어있다.

inside
outside

의자에 대해 작업을 진행할 때, 맞닿아있는 면을 찾아내는 작업을 진행했었다. voronoi fracture에서는 그런 작업을 할 필요없이 바로 맞닿은 면을 group으로 만들어놓은 것이다.

 

prim number가 겹친것이 보인다.

inside 중 blast로 떼어준 두개의 같은 위치의 primitive에 대해 packing해주면서 각각의 name을 사용할 수 있도록 pack parameter에서 빼주었다.

이렇게 빼낸 @name은 primitive 정보이기 때문에 attribute promote로 point로 옮겨주고, add 노드를 활용해서 primitive를 날려주면 각 물체를 대변할 수 있는 point만 남게 된다.

이 작업을 inside 면의 각각의 면에 대해 작업을 진행하면...

하지만 현재는 constraint를 만들어줄 primitive 정보가 없다.

같은 위치의 두 점을 이어주기 위해 sort로 by x(혹은 by y)를 사용해서 같은 곳에 있을 점들로 정렬해주고,

add 노드의 polygons > by group 에서 Add parameter를 Groups on N points 로 설정해준다.

  • 여기에서 N은 몇개씩 그룹으로 묶을 것이라는 것이다.(N : 2일 경우 2개 포인트를 하나로, 3일 경우 3개 포인트를 하나로)

 

완벽하지는 않은것 같다. 위치 정보 x가 같은 것을 묶을 때, 혹은 y가 같은 것을 묶을 때, 완벽하게 x가 (혹은 y가) 같은 위치에 있지만 다른 값이 다른 포인트들이 존재할 경우, 불필요한 primitive가 생성된다.

by x의 결과, 불필요한 60, 66 primitive가 생성되었다.

 

voronoi fracture를 사용할 경우, 기본적으로 constraint 노드를 제공해준다. 근데, 아쉬운 부분이 있다.

이게 있다!!

이게 참 유용하지만, 면과 면이 맞닿은 접착과 같은 느낌을 표현하기 애매하다. 작은 조각의 경우, 시뮬레이션이 진행되면서 공중에 떠있는 경우도 존재한다.

그래서 기왕이면 restlength가 0이 되도록(접하는 부분에 constraint가 생성되도록) 만들어주려는 것이다.

 

그렇다면 위의 내용을 가지고 restlength가 0이 되도록 해주면 어떻게 될까?

접한 면에 만들어준 결과.
voronoi fracture의 constraint output에 primitive 노드에서 scale 0으로 만든 결과

오차가 꽤 커보인다.

 

결국 무언가 작업을 위해서는 접한면의 포인트를 알아야할 때가 온다.

voronoi fracture가 제공하는 constraint를 사용하는것도 좋지만, 접한면의 포인트를 구해서 만들어준 방법, 알아두면 도움이 될 것이다.


이번에는 voronoi fracture와 의자 모두 적용 가능한 시스템을 만들어보자.

 

결국 아이디어 싸움이다.

논리적으로 합리적이어야하고,

아무리 합리적이라고 해도 후디니가 계산하는데 너무 오래 걸린다면 그것은 합리적이라고 할 수 없게 된다.

최적화가 안되었다는 것이다.

 

 

이번에 해볼 작업은, 물체에 많은 점을 뿌리고, 접하는 부분이 있는지 확인하는 것이다.

 

각각의 물체에 점을 뿌려주고, 점에 서로 다른 색을 부여했다.

거리가 충분히 가까울 때 seat의 빨간색이 다리의 포인트에 넘어갈 수 있도록 한다면?

 

attribute transfer를 사용해서 확인해보자.

위의 빨간 점들만을 가지고 중심을 유추해보자.

 

fuse 노드를 활용한다.

  • snap distance 거리 안쪽의 포인트들을 하나로 합친다.

 

이 방법은 사용자의 컴퓨터 사양만 충분하다면, 정확함에 근접하는 중심점을 얻어내기 위해 scatter로 뿌려주는 포인트의 개수만 조절해주면 된다.

대신에 조각의 수량이 많아질수록, 계산속도 또한 어마어마하게 늘어나버린다.

그리고 물체들의 사이즈에 따라서 문제가 발생할 수도 있다.(물체의 사이즈에 따라 scatter로 인해 포인트가 뿌려질 때 밀도가 서로 다를 수 있다)

밀도 차이로 인해서 중심점을 구하기 위한 포인트가 충분히 확보되지 못할수도 있다.(중심점에 대한 오차가 발생한다)

 

 

의자에 적용해서 각각의 조각에 대해서 만들어지는지 확인해보자.

  • 아까와 마찬가지로 메인 조각 하나를 각각의 조각에 대해 비교하는 for each 중첩 방식을 활용한다.

메인 조각을 제외한 나머지 조각군을 만들어주는 것은 다음과 같다.

얼추 그럴싸한 위치에 만들어졌다.

 

voronoi fracture에 적용해보자.

약간의 오차들이 눈에 보인다.

이 오차들은 잘못구해진 것일까?

그리고, 위의 경우에서 constraint를 위해 필요한 primitive의 개수는 8개이지만, node info를 확인해보면 16개, 즉, 우리가 필요로 하는 것의 2배수의 primitive를 가지고 있는 것을 확인할 수 있다.

 

오차로 보이는 부분의 primitive를 떼어서 확인해보자.

0번을 메인조각으로 해서 관계를 만들어내다가 3번 조각과 관계가 만들어졌고,

3번을 메인조각으로 해서 관계를 만들어내다가 0번 조각과 관계가 만들어졌다.

0번과 3번 둘 사이에 있어서 연결관계는 한번만 만들어내면 족할듯 싶다.

 

조각이 많아지면 시간은 급격하게 늘어난다.

 

연결의 중복을 제거하기 위한 방법은 어떤 방법이 있을까?

현재 계산되는 경우의 수이다.

이 상황에서 우리가 필요한 것은 0-3번의 경우이던, 3-0의 경우이던, 0번 조각과 3번조각의 연결 하나면 충분하다.

주황색으로 표기된 계산을 스킵해서 결과적으로는

이렇게 계산해줄 수 있을것이다. 어떻게 해주면 될까?

get id로 불러온 메인 조각의 id + 1보다 작은 조각들에 대해서는 계산을 스킵해주면 될 것이다.

  • get id+1보다 작은 경우는 결국 get id 또한 포함이다.
    • ex) get id = 0일 경우, 0+1 보다 작은 @id 는 날려버리기 때문에 @id = 0 은 날아간다.

 

한가지 더, 메인 조각에 대해서 일정 반경 안의 조각에 대해서만 계산을 진행해주면, 그리고 일정 반경 이상의 조각은 계산을 스킵해준다면, 계산의 횟수가 더욱 줄어들 수 있을 것이다.


지금까지 작업한 constraint 시스템의 아쉬움

  1. 하나하나 수작업으로 진행해야하는 아쉬움
  2. boolean이 발생시키는 오류가 존재
  3. inside 정보를 활용해야했기 때문에 일반 의자에서는 작동을 하지 않았다.
  4. 표면에 뿌리는 점의 수량을 바꿔가면서 오차가 발생하지 않도록 해줘야했고, 결국 느렸다.

 

이번에 해볼 방법은, 일단 메인 조각의 각 표면의 중심점을 구해주고, 어떤 면이 맞닿아있는지 확인하는 방법이다.

 

각각의 primitive를 확인하기 위해서 for each primitive 블럭을 사용한다.

measure 노드를 이용해서 measure > centroid 를 적용해준다.

primitive에 centroid에 대한 정보가 저장되었다.

measure로 구해준 centroid 위치정보를 가지고 포인트를 만들어준다.

 

이번 작업의 핵심이 되는 아이디어는, 위에서 구한 각 면의 중심이 레퍼런스 물체 중에서 가장 가까운 곳으로 이동한다면 어떻게 될지가 핵심이다.

 

ray 노드를 활용해준다.

각 점을 레퍼런스 물체의 가장 가까운 곳으로 투사시킬 경우, 면과 면이 맞닿아있는 부분은 면을 공유하고 있기 때문에, 투사되는 점의 위치가 같거나 거의 이동하지 않은것처럼 보이게 될 것이다.

5번이 제일 궁금하다.

두 점을 merge 해본 결과이다. 위치에 차이가 없다.

 

ray의 parameter 중, 원본 포인트가 ray 노드에 의해 얼만큼 이동하였는지 이동값을 저장해줄 수 있는 부분이 있다.

  • point intersection distance를 활성화해주면 된다.

각각의 이동한 거리가 f@dist로 저장된 것이 확인된다.

이제 불필요하게 @dist가 큰 포인트는 날려버리자.

 

ray parameter 중 하나 더 유용한 것이 있다.

원본 포인트가 ray에 의해 레퍼런스 물체에 투사될 때, 투사된 포인트로 레퍼런스 물체의 attribute를 전달받을 수 있다.

이것을 활용해서 포인트가 대변하게 될 물체의 이름을 얻어올 수 있다.

이것으로 포인트 하나의 이름을 해결했고,

메인조각의 이름은 attribute copy를 사용해서 넣어준다.


반복작업을 하나 더 진행해보자.

각각의 조각에 대해 비교대조를 진행하는데 나 자신에 대해서는 비교 대조를 할 필요가 없을 것이고, meta data와 blast를 적극 활용하는 방법이다.

주의할 점!!! blast에 들어가는 정보는 현재 타입이 string이다.

우리가 입력해줘야하는 정보가 integer인데, string 에 기입해줘야하는 경우, "`"을 앞에 적어주면 된다.

  • ` > 이 표시는 숫자 1번 옆의 물결표시 키와 함께 쓰인다.

이 세팅은 현재 voronoi fracture의 결과에서는 중복된 연결을 만들어낸다.

그렇다면 의자에서는?

 

선생님의 결과는 중복되는 점이 없이 나왔다.

그 이유로는 맞닿는 면의 사이즈로 인해서 면의 중심이 이동하게 되면서 dist가 발생하고, 그에 따라서 dist가 발생한 점을 지우게 되면서 상대적으로 넓은 면의 중심 포인트가 사라지면서 costraint 활용을 위한 primitive가 생성되지 않았다.

 

(내 작업물의 경우는 중복포인트가 두개 나왔다.)

수정이 필요하다...

voronoi fracture 결과에 대해 우선 작업을 진행해보자.

11개의 primitive 연결관계가 나와야하지만 현재 node info는 22개를 가지고 있다고 나오고 있다.

point에 대해 assemble 로 packing을 해준다.

point의 숫자가 절반으로 줄어들었다.

fuse 노드로 같은 위치의 겹친 포인트들을 합쳐준다.

fuse로 합쳐준 point에 primitive 정보가 두개씩 남아있는 상황이다. 날려버리자. 그리고 point attribute의 @name 도 assemble 작업을 하면서 만들어진 이름정보이기 때문에 별 필요가 없다. 같이 날리자.

이제 할 것은, 각각의 포인트들에 대해 거리값을 구해서 거리가 0 혹은 0과 매우 가까운 점만 남겨놓고 날려버릴 것이다.

일단 0번 포인트를 가지고 각 포인트들과의 거리를 계산해줬다.

그리고 @dist를 기준으로 sort해주고, 가장 값이 작은 0번만을 활용하도록 세팅했다.

이렇게 나온 0번 포인트는 현재 packing된 정보이고, 이것을 unpacking해주면 우리가 원하는 물체간의 연결을 위한 포인트, 그리고 그 포인트로 만들어낸 primitive를 얻어낼 수 있다.

 

이것을 for each point 블럭을 활용해서 각각의 포인트들에 대해 적용해주면 원하는 constraint 를 위한 primitive 개수에 맞는 primitive가 구해진다.


세팅은 끝났지만, 한가지 더, 접착력에 대한 정보를 남기고 싶다.

각각의 constraint가 가지는 결합의 세기가 모두 동일하다면, 시뮬레이션에서 부자연스러운 결과를 도출하게 될 것이다. 이것을 방지하기 위해서 각기 다른 접착력을 가지도록 세팅해보자.

 

접착력을 위해서는 맞닿는 면의 너비를 아는 것이 필요하다.

면의 너비 중, 조금이라도 작은 면의 너비를 사용해서 접착력을 계산한다.

 

1. 일단 위의 시스템으로 만들어준 연결된 primitive를 떼어낸다.

  • 떼어낸 primitive는 2개의 포인트를 가지게 되고, 각각 물체의 고유한 이름을 가지고 있다.

2. 각 포인트에 대해서 이름이 같은 조각을 떼어낸다.

  • 떼어낸 조각은 unpacking을 해주고, 각 primitive에 대해 @id를 부여한다.(i@id = @primnum)

3. 1에서 떼어낸 prim의 포인트 중 현재 작업중인 포인트를 ray 노드를 사용해서 2의 조각에 투사시킨다.

  • 이렇게 되면, 투사된 면의 @id를 얻어낼 수 있다.
  • 이때의 @id는 @primnum으로 만들어줬기 때문에, 결국 맞닿는 면의 primitive 번호를 얻을 수 있다.

4. 3에서 구한 번호를 비교대조해서 2의 조각 중 id가 같지 않은 primitive를 전부 날려버린다.

  • @id가 같은 하나의 primitive만 남길 수 있다.

5. 구해준 primitive를 measure 노드를 사용해서 너비를 구해준다.

6. 1에서 구해줬던 포인트 중, 아직 작업안한 포인트에 대해 2~5번을 작업해준다.

7. 두 primitive의 면적을 비교해서 더 작은 면적을 primitive 정보로 1번의 primitive에 넘겨준다.

 

각각의 primitive에 대해 구해주는 것이기 때문에 for each primitive 블럭을 사용한다.

voronoi fracture에도 잘 적용되는지 확인해보자.

잘 적용되는듯 하다.


결과로 얻게 되는 데이터를 분석해보자.

필요없는 정보들은 날려주는 것이 좋을 듯 하다.

@dist : @restlength로 활용이 가능할듯 싶다. primitive 정보로 넘겨주자.

@name : 필요없는 정보다. 날리자.


진짜 아이디어 싸움이라는 말을 다시 한번 실감하게 된다.

지금 현재 상태로서는 죽었다 깨어나도 고민을 해보아도 이런 방법을 생각해낼 머리가 없다...ㅠ

(아 슬프다...)

아이디어를 위해 머리가 굳지 않게 열심히 굴려보자. 그리고 많이 고민하고 생각해내자.

 

 

참고로... 의자에서 constraint 가 등받이 쪽에서 두개가 더 나온 이유는 등받이의 위치가 등받이 기둥면의 중심에 가까워서 ray로 투사된것이 아닌가 싶다. 등받이 위치가 달라지니 추가로 생성되었던 두개의 점 또한 사라졌지만, 이 부분에 대해서는 조금 고민을 해봐야겠다. 정 안되면 다른 방법을 사용해서 둘 중 더 나은 primitive를 사용할 수 있도록 할 수 있을 것 같기도 하다.