Houdini

RBD 2nd Simulation

Tedd_Kim 2022. 6. 10. 11:00

두 번째 시뮬레이션을 진행하는 이유는 Constraints가 다 깨진 rbd piece들을 대상으로 큰 충격이 있었을 시, 또 다른 다이나믹을 추가하기 위해서이다. 다만, 원본 시뮬레이션의 다이나믹을 깨지 않는 선에서 해상도를 조금 더 추가하는 개념으로 생각해야함

 

이를 달성하기 위해서는

1. Constraints가 깨져있는지 확인해야함

2. 2차적인 충돌을 확인하기 위하여 impact데이터를 사용해야함

3. 너무 자잘한 piece을 대상으로는 2번째 fracture를 진행할 가치가 없음

4. 어느정도 다이나믹을 유지하기 위해서 원본 piece의 v, w값을 가져와야하고, 충돌이 있은 후에 2-3프레임 정도는 원본 모션을 따라가도록 해야함 (animated attribute)

5. 2차 fracture를 진행한 모든 sub-piece들이 한번에 active되는 것이 아니라, 충돌 지점과 가장 가까운 piece들만 active되어야함

 

위 조건들이 필요하기 때문에 순차적으로 해결해 나가면서 진행하면 된다.

조금 복잡한 과정이긴 하지만 모션 자체는 훨씬 현실적이다.

 

사진은 나중에 추가

 

[ 1st Simulation ]

note : rbd시뮬레이션은 volume collision을 사용하지 않는다. 그러니 충돌체가 있다면 vdb를 vdb to shphere, convert 등을 사용해서 적당한 proxy를 생성해야함. 이걸 이제 알다니..

 

이 단계에서 해야할 것은, 최대한 첫 번째 시뮬레이션만으로 좋은 룩을 뽑는 것이다.

그리고 캐싱을 할 때에 

1. 시뮬레이션 포인트

2. constraints 

3. dop import records노드를 이용해서 가져온 impact데이터

이 세가지에 각각 그룹을 달아주고 merge시켜서 캐싱을 해야한다 

 

[ Constraints check ]

Constraints에 transfrom piece노드를 이용하여 모션을 가져오고, broken그룹만 남긴다.

pack된 시뮬레이션 Piece를 첫 번째 인풋에, broken된 constraints를 두 번째 인풋에 넣고 wrangle을 다음같이 작성한다

 

int pt = findattribval(1, "point", "name", s@name); //같은 name을 가진 ptnum을 constraints에서 찾아옴, 만약 이미 깨지고 없다면 -1을 리턴 할 것이다.

string broken = point(1, "name", pt); // 상응하는 ptnum의 name문자열을 가져온다

if(!match("piece*", broken)) {

    removepoint(0, @ptnum); // name이 없는(=깨지고 없는 piece들을 다 삭제시킴)

}

 

constraints는 순차적으로 깨지고 있던 놈이 갑자기 생길 수 없기 때문에 solver를 쓰지 않아도 알아서 누적 된다

따라서 이후엔 time shift를 이용하여 맨 마지막 프레임에 고정시키면 시뮬레이션 시간동안 constraints가 깨진 piece들만 남게 된다.

nametoprim nametopoint라는 함수가 같은 기능을 한다는데 그냥 그렇다고..

 

[ impulse check ]

여기서 조금 복잡해진다. 

일단 Impact데이터에서 쓸만한 어트리뷰트는 

1. objid : 시뮬레이션 piece 즉, rbd object가 가지고 있는 고유한 정수형 숫자이다. 하나의 obejct만 추출했으면 무조건 하나의 고유값

2. otherobjid : 충돌한 다른 오브젝트들의 정수값이다. 이건 dop network에서 확인 가능

3. otherobjprimnum : 충돌의 대상이 된 오브젝트의 primnum인데, 일단 별 관심 없다.

4. primnum : rbd object의 primnum, 즉 ptnum이다. 굉장히 중요

5. impulse : 충돌의 세기를 나타낸 값이다. glue를 깨는 Impact와는 조금 다른 것 같음. 아무튼 이것도 중요하다

6. time : 시뮬레이션 time step같은데 솔직히 잘 모르겠음, 해당 프레임에서 값이 왜 여러개인지도 모르겠고 sidefx에서도 설명 안써줬다

7. normal : 충돌의 대상이 되는 오브젝트가 가지고 있는 normal방향, 어떻게 써먹을 순 있을듯. 그 반대로 튕겨낸다던지..

나머지 어트리뷰트들은 쓸 가치가 없기 때문에 다 버리면 된다.

 

ground, collider, self충돌 모두 강한 Impact가 생기면 2차 시뮬레이션을 가할 것이기 때문에, 따로 관리할 것이 아니라면 전부 merge된 상태로 진행

 

첫 번째로, pscale을 impulse * 임의값으로 정해서 뷰포트로 확인하며 작업하면 편하다.

그리고 이상하게 Impact데이터는 ptnum을 이용할 수가 없기 때문에 어떤 경우의 삭제를 진행하기 전에 enumerate노드를 사용하여 ptnum으로 활용할 어트리뷰트를 하나 만든다.

 

두 번째로, Impulse를 기준으로 본인의 성향에 따라 포인트들을 걸러준다

그러면 대망의 주인공이 등장

 

일단 목표는 어떤 rbd object의 piece가 충돌했는지 알아내야한다. 그렇기 때문에 충돌한 포인트가 가지고 있는 primnum 어트리뷰트를 기록해야함. 

 

int empty[] = {}; 

for(int i=0; i<npoints(0); i++) { // impact포인트 전체를 대상으로 루프 진행

    int num = point(0, "primnum", i);

    push(empty, num); //충돌한 모든 piece들의 primnum을 배열에 삽입

}

 

여기까지 하면, 배열에 중복된 숫자들과 정렬이 난리가 난 상태로 들어온다.

따라서 중복을 지워주기 위해서 배열을 오름차순 정렬하고, 새로운 배열에 원소를 넣는 과정에서 중복이 발생하면 스킵하는 형식으로 해야함. 그런데, 후디니에는 배열에서 중복되는 원소를 지워주는 함수가 없다! 그래서 직접 만들어야 함

 

empty = sort(empty); //일단 중복 서치를 위해 배열을 오름차순 정렬한다

i[]@new = {}; // 새로운 배열 하나 생성, 이 배열은 이후에 cast해야하기 때문에 @를 붙여서 작업해야함

 

void deleteplural(int oldArray[]; int newArray[]) {

// 리턴타입을 배열로 해주는 기능은 없다

// 인자를 2개 이상 받을 때에는 콤마가 아니라 세미콜론을 찍어야함

    int length = len(oldArray); // 기존에 있는 배열의 길이를 구함

    for(int j=0; j<length; j++) {

        if(j==0) {

            push(newArray, oldArray[j]); // 첫 번째 원소는 중복일리가 없으니 그냥 삽입

        } else { // 두 번째 원소부터

            if(oldArray[j] == oldArray[j-1]) { // 만약 새로 넣을 원소가 이전 원소랑 같으면(중복이면)

                continue; // 배열에 삽입하지 않음

            } else { // 중복 아니면 배열에 삽입

                push(newArray, oldArray[j]);

            }

        }

    }

}

deletePlural(empty, @new); // 우리가 만든 작고 소중한 함수로 new함수를 생성한다.

이렇게 하면 해당 프레임에서 충돌이 있었던 모든 포인트의 primnum을 하나의 배열에 저장하게 된다. 배열을 왜 썼냐면 곧 알게됨

 

이제 시뮬레이션 포인트를 불러와서 enumerate노드를 사용해주고 

이걸 첫 번째 인풋, 방금까지 한 impact포인트를 두 번째 인풋에 넣고 다음과 같이 wrangle을 작성한다

 

i[]@array = pointattrib(1, "new", 0, 0); // 배열은 저 함수를 써서 가져올 수 있다

 

for(int i=0; i<len(@array); i++) { // 각각 시뮬레이션 포인트마다 배열을 루프

    if(i@__numpt == int(@array[i])) { // 만약 본인의 ptnum이 array의 원소, 즉 충돌한 오브젝트의 primnum과 같다면

        i@re = 1; //refracture하겠다는 의미로 정수하나 생성

        @group_refracture = 1; // 기왕 그룹도 생성

    }

}

 

그리고 attribute copy를 이용하여 constraints로 한번 걸러진 pack geometry를 첫 번째 인풋, 방금 진행한 시뮬레이션 포인트를 두 번째 인풋으로 넣고, name으로 매칭을 시킨 채 re어트리뷰트를 복사해준다.

그리고 activeFrame과 re어트리뷰트를 계속하여 누적해주는 solver를 하나 돌려서 time shift를 맨 마지막 프레임으로 보내준다

 

여기까지 하면 constraints와 impulse를 이용한 거르는 작업이 끝난다.

 

[ Area check ]

이후에 blast를 사용해 re어트리뷰트를 기준으로 다시 걸러주고, 간단하게 area어트리뷰트를 사용해서 너무 작은 piece들을 재차 걸러주면 끝.

 

[ 2nd Fracture ]

이 과정에서 현재 메인 스트림은 pack된, time-nondependent인 시뮬레이션 지오메트리이다.

열심히 걸러준 piece들을 unpack하고 대망의 2차 fracture를 진행한다.

name관리에 유의하고, s@name_orig = split(s@name, "_")[0]; 같은 코드를 통해 본래의 name을 저장해주고 다시 assemble하여 pack시킨 다음 transform piece를 통해 모션을 복귀시켜준다.

 

[ Acitve & Animated ]

impulse로 삭제시킨 impact포인트를 불러와서 첫 번째 인풋, 방금 복귀된 모션의 pack지오메트리를 두 번째 인풋에 넣고 다음과 같이 wrangle을 작성한다

 

float radius = chf("max_radius");

int max_pts = chi("max_pts");

 

i@val = pcfind(1, "P", @P, radius, maxpts)[0]; // 충돌 지점과 가장 가깝게 위치한 piece의 ptnum을 가져온다

pcopen해서 pciterate, pcimport로도 할 순 있는데 이건 pcfind가 훨씬 편함

 

다음번 wrangle에서 단독 인풋으로, val의 모든 숫자를 하나의 배열에 넣을 것인데

여기서 또 중복이 나오기 때문에 아까 만들어둔 함수를 다시 사용해야함

 

int array[] = {};

for(int i=0; i<npoints(0); i++) {

    int pt = point(0, "val", i); //모든 임팩트 포인트를 대상으로 가지고 있는 val을 싹 다 가져옴

    push(array, pt); //배열에 저장

}

 

i[]@new = {};

deletePlural(array, @new);

 

이제 얘들 바로 active시켜줘도 되지만 그러면 충돌하는 순간 바로 active가 되기 때문에, 원래 가지고 있던 모션을 별로 유지하지 못한채 시뮬레이션이 된다. 따라서 active가 아닌 candidate그룹으로 포함시킬 것이고, 원래 가지고 있던 activeFrame어트리뷰트에 임의의 프레임수만큼 더해서 그 두가지 조건을 만족하면 active시킬 것이다.

 

일단 모션을 가지고 있는 pack geometry를 첫 번째 인풋에, 방금까지 작업한 impact포인트를 두 번째 인풋에 넣고 다음과 같이 wrangle을 작성한다

 

i[]@array = pointattrib(1, "new", 0, 0);

 

for(int i=0; i<len(@array); i++) {

    if(@ptnum == @array[i]) {

        i@candidate = 1;

    }

}

이후에 solver를 사용해서 한번 candidate가 된 녀셕들은 계속 유지할 수 있도록 해주고

모션을 가진 pack geometry에 name을 매칭시켜서 candidate어트리뷰트를 attrib copy해준다

 

그리고 마지막으로 wrangle을 작성하여

int activeAfter = chi("frame");

if(activeAfter + i@activeFrame < @Frame && i@candidate == 1) {

    i@active=1; // activeFrame후 몇프레임 있다가, 그리고 candidate라는 어트리뷰트를 가지고 있다면 그제서야 active시킴

}

 

if(@active == 1) {

     i@animated = 0;

} else {

     @animated = 1;

}

 

이렇게 하면 sub-fracture한 모든 piece들이 한번에 active되는 것이 아니라, 충돌 지점과 가장 가까운 piece들만 active가 된다.

이후에 다시 sim point들을 모으고, 새로운 piece들을 모아서 하나의 캐시로 관리하면 됨

 

조금 복잡한 과정이긴해도 일단 두 번째 시뮬레이션의 모션을 확인하는 순간 모든 것이 용서된다.

왜냐하면 2차, 3차 모션은 절대 glue constraints만으로 달성할 수 없기 때문