개발 / / 2024. 5. 16. 09:18

PBD cloth simulation 코드 분석 (Cloth Self Collision solve) [Threejs Cloth Tailor 개발일지]

반응형

 

 

PBD에서 self collision을 위한 충돌처리 class를 살펴보고 시뮬레이션의 어느 과정에서 self collision을 처리하는지 알아보도록 하자

 

 

Collision class

export abstract class Collision {
  protected positions: Float32Array;
  protected prevPositions: Float32Array;
  protected invMass: Float32Array;
  protected vecs: Float32Array;
  protected numParticles: number;

  constructor(
    positions: Float32Array,
    prevPositions: Float32Array,
    invMass: Float32Array
  ) {
    this.positions = positions;
    this.prevPositions = prevPositions;
    this.invMass = invMass;
    this.vecs = new Float32Array(12);
    this.numParticles = positions.length / 3;
  }

  /**
   * Updates positions during an animation step
   */
  abstract solve(dt: number): void;
}

collision class 자체는 추상클래스이기 때문에 solve 함수 하나와 변수들만이 선언되었다

움직임에 따른 충돌을 검출하므로 prevPositions과 positions, 움직임의 방향 vecs를 포함한다

solve

thickness

export default class ClothSelfCollision extends Collision {
...
  solve(dt: number) {
    // Square to compare with dist2
    // We can do this to save a sqrt operation
    const thickness2 = this.thickness * this.thickness;

    for (let id0 = 0; id0 < this.numParticles; id0++) {
      if (this.invMass[id0] == 0.0) continue;
      const adjacentParticles = this.hash.getAdjacentParticles(id0);

      for (const id1 of adjacentParticles) {
        if (this.invMass[id1] == 0.0) continue;

        // Determine if the distance between the two particles is smaller than
        // the thickness... which would signify that the particles are overlapping
        // each other.
        vecSetDiff(this.vecs, 0, this.positions, id1, this.positions, id0);
        const dist2 = vecLengthSquared(this.vecs, 0);
        if (dist2 > thickness2 || dist2 === 0.0) continue;

        // If the particles have smaller rest distances than
        // the thickness, use that to make the position correction.
        const restDist2 = vecDistSquared(
          this.restPositions,
          id0,
          this.restPositions,
          id1
        );

        let minDist = this.thickness;
        if (dist2 > restDist2) continue;
        if (restDist2 < thickness2) minDist = Math.sqrt(restDist2);

thickness는 ClothSelfCollision에 선언된 옷의 두께이다

두께의 제곱와 particle의 distance 제곱 자체를 비교해서

마지막에 사용할 sqrt 연산을 줄이는 방법이다

 

    for (let id0 = 0; id0 < this.numParticles; id0++) {
      if (this.invMass[id0] == 0.0) continue;
      const adjacentParticles = this.hash.getAdjacentParticles(id0);

      for (const id1 of adjacentParticles) {
        if (this.invMass[id1] == 0.0) continue;

각 particle을 순회할 때 역질량 invMass를 체크하는 것은

역질량은 결국 constraint 수식에서 분모로 사용되기 때문이다

 

hash

    const adjacentParticles = this.hash.getAdjacentParticles(id0);

도중에 인접 particle을 구할 때 hash방법을 사용한다 

파티클 시뮬레이션 기법들에서는 연산량이 상당하기 때문에

hash방법 및 병렬처리가 가능케하는 다양한 방법들이 불가피하게 사용된다

 

hash class에서 인접 particle을 가져오는 방법에 대해서는 다음 포스팅에서 다루도록 하자

 

adjacent particles

collision solve에서는 각각의 인접 particle들에 대해서 충돌이라는 기준을 세우고 판단하는 역할을 하는데

 

    // Determine if the distance between the two particles is smaller than
    // the thickness... which would signify that the particles are overlapping
    // each other.
    vecSetDiff(this.vecs, 0, this.positions, id1, this.positions, id0);
    const dist2 = vecLengthSquared(this.vecs, 0);
    if (dist2 > thickness2 || dist2 === 0.0) continue;

distance와 thickness를 비교해서 옷의 두께보다 거리가 가까우면 particle이 overlapping 즉 겹쳤다고 판단한다

 

solve 과정에서는 충돌에 대한 처리를 하기 때문에 거리가 두께보다 멀거나

(dist2 = 0) 같은 particle에 대해서 비교하고 있을 때는 충돌하지 않은 것으로 간주하고 넘긴다

 

    // If the particles have smaller rest distances than
    // the thickness, use that to make the position correction.
    const restDist2 = vecDistSquared(
      this.restPositions,
      id0,
      this.restPositions,
      id1
    );

현재 중심이 되는 id0 particle과 인접한 주변 particle인 id1 particle과의 거리제곱을 구한다

 

단편적으로 보면 위에서 dist2의 길이 두 particle의 거리를 비교하였는데,

여기서도 비슷하게 vecDistSquared를 통해서 각 particle 사이의 거리를 비교하고 있다

 

다른 점은 rest position이라는 점이다

정지 위치라는 것은 solve를 시작할 때의 위치를 말한다

position의 경우 이후 속력 vector에 의해 위치가 업데이트되고 rest는 그대로이다

 

    let minDist = this.thickness;
    if (dist2 > restDist2) continue;
    if (restDist2 < thickness2) minDist = Math.sqrt(restDist2);

옷의 thickness를 최소 distance를 설정하는데

이 minDist는 이후 충돌로 삐져나간 particle을 정상적인 자리로 옮기기 위한 correctionDist 값에 사용된다

 

distance가 rest distance보다 큰 경우 충돌이 아니라 오히려 멀어지는 것이므로 스킵

두께보다 restDist값이 작으면 minDist를 수정한다

 

correctionDist

    // Position correction
    // Now finally do the sqrt op
    const dist = Math.sqrt(dist2);
    const correctionDist = minDist - dist;
    if (correctionDist > 0.0) {
      vecScale(this.vecs, 0, correctionDist / dist);
      vecAdd(this.positions, id0, this.vecs, 0, -0.5);
      vecAdd(this.positions, id1, this.vecs, 0, 0.5);

충돌을 넘어 지나간 거리 dist만큼을 최소 유지해야하는 거리 minDist에서 빼서

이동해야하는 correctionDist를 구한다

 

correctionDist는 아마 dist의 경우 충돌을 지나서 방향이 반대방향이 되기 때문에 minDist와 dist의 절댓값 합이 될 것이다

즉 dist는 보통 (-) 부호가 될 것이고 충돌을 하여 뚫고나간 상황에서 correctionDist는 양수가 된어 if문 안으로 들어간다

 

correctionDist / dist를 하면 particle이 충돌하여 뚫고나간 상황에서 얼마나 빠른 속도로 돌아와야하는지 속력을 구할 수 있다

이 속력을 주체가 되는 particle id0과 인접 particle id1이 서로 반대 방향으로 가진다

Friction (damping)

  // Friction Handling
  const dampingCoefficient = -1;

  if (dampingCoefficient > 0) {
    // velocities
    vecSetDiff(
      this.vecs,
      0,
      this.positions,
      id0,
      this.prevPositions,
      id0
    );
    vecSetDiff(
      this.vecs,
      1,
      this.positions,
      id1,
      this.prevPositions,
      id1
    );

-1의 마찰계수를 가지고 속력이 점차 줄어드는 damping을 처리한다

이 과정에서 vecs 0번 1번 속력이 정해진다

 

// average velocity
vecSetSum(this.vecs, 2, this.vecs, 0, this.vecs, 1, 0.5);

// velocity corrections by modifying them.
vecSetDiff(this.vecs, 0, this.vecs, 2, this.vecs, 0);
vecSetDiff(this.vecs, 1, this.vecs, 2, this.vecs, 1);

// add corrections
vecAdd(this.positions, id0, this.vecs, 0, dampingCoefficient);
vecAdd(this.positions, id1, this.vecs, 1, dampingCoefficient);

average의 경우 인덱스로 보면 0번 1번 속력으로 평균낸 다음 2번 속력을 구한다

2번 인덱스에는 0번 1번의 평균이 들어가고, 각각 1씩 증가해서 3번(1,2번) 4번(2,3번)을 쭉 진행한다

 

정확히 어떤 원리인지는 모르겠으나

vecSetDiff 과정까지 거친 속력으로 position에 속력을 구하는 과정이 속도를 damping하는 수식을 구현한 것에 해당한다

반응형
  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유