SAPIEN 접촉 충격량을 힘으로 읽기: 부호와 시간 간격이 필요한 이유

SAPIEN의 접촉점에 저장된 impulse는 힘 자체가 아니라 한 시뮬레이션 스텝 동안의 충격량입니다. 특정 물체가 받은 접촉력을 구하려면 그 물체가 접촉 쌍의 어느 쪽인지에 따라 부호를 맞추고, 관련된 모든 접촉점의 충격량을 합한 뒤 그 스텝의 시간 간격으로 나눠야 합니다. 한 접촉점의 숫자나 접촉 목록 길이만으로 물체를 받치는 힘을 읽을 수는 없습니다.

SAPIEN 공식 Contact 예제는 아래 상자가 위 상자를 받치는 장면으로 이 계산을 보여줍니다. 이 글은 2026년 10월 15일 확인한 문서와 그 안의 전체 예제 코드를 해석합니다. SAPIEN을 실행해 접촉값을 수집하거나 실제 힘 센서와 비교하지 않았습니다.

어느 물체에 작용한 충격량인가

공식 문서는 point.impulse를 접촉 쌍의 첫 번째 물체에 작용한 충격량으로 설명합니다. 관심 있는 물체가 첫 번째라면 반환값을 그대로 쓰고, 두 번째라면 방향을 반대로 바꿉니다. 부호는 이름에 붙어 있는 고정 속성이 아니라 접촉 쌍의 순서와 측정 대상의 관계입니다.

예제는 위쪽 동적 상자 box2가 받는 수직 지지력을 계산합니다. 첫 번째 물체가 box2이면 impulse[2] / dt를 더하고, box1이면 같은 값을 뺍니다. 두 상자만 있는 장면에서는 첫 번째가 아래 상자라는 사실로 두 번째가 위 상자임을 알 수 있습니다.

접촉 쌍에서 대상 물체의 위치 대상에 작용한 충격량으로 변환 합산 여부
첫 번째 반환된 impulse 유지 포함
두 번째 반환된 impulse의 부호 반전 포함
어느 쪽에도 없음 대상 물체의 접촉이 아님 제외

물체가 많은 장면에 예제의 이름 분기만 옮기면 안 됩니다. 첫 번째 물체가 box1이라고 해서 상대가 항상 box2인 것은 아닙니다. 두 참여 물체를 모두 확인해 대상을 고른 뒤 부호를 정해야 합니다. 같은 이름을 여러 물체에 썼다면 이름만으로도 충분하지 않습니다.

또한 이 문서에는 API 명칭 차이가 있습니다. 설명문은 contact.components[0]과 [1]을 소개하지만, 포함된 코드는 contact.bodies[0].entity.name을 사용합니다. 계산의 부호는 첫 번째 참여 물체를 기준으로 정합니다. 코드를 옮길 때는 설치한 SAPIEN 버전에서 반환하는 객체가 components와 bodies 중 어느 이름을 제공하는지 먼저 확인하세요.

여러 접촉점은 같은 기준으로 합산합니다

하나의 물체 쌍에서도 contact.points에는 여러 접촉점이 들어갈 수 있습니다. 공식 예제는 접촉마다 모든 점을 순회해 수직 충격량을 더합니다. 첫 점만 읽으면 그 점에 배분된 몫만 보고 전체 지지력을 놓칠 수 있습니다.

예제처럼 수직 성분을 구한다면 계산은 F_target,z = (Σ s_i × J_i,z) / Δt로 적을 수 있습니다. J_i,z는 각 점에서 반환된 z 성분, s_i는 대상이 첫 번째이면 +1이고 두 번째이면 −1인 부호입니다. 대상과 무관한 접촉점은 이 합에 넣지 않습니다. SI 단위를 사용한 장면에서는 충격량의 단위 N·s를 시간 s로 나눠 평균 힘 N을 얻습니다.

가상의 두 접촉점이 대상 기준으로 각각 위쪽 충격량 0.04 N·s와 0.06 N·s를 전달했다고 해 보겠습니다. 둘을 합하면 0.10 N·s입니다. 반환 쌍의 순서가 달랐다면 부호를 바꾼 뒤 더해야 합니다. 각 벡터의 크기를 먼저 구해 모두 더하는 계산은 방향이 반대인 기여까지 양수로 만들므로, 대상 물체가 받은 벡터 합력과 다른 값을 냅니다.

무엇을 합할지도 힘의 이름을 결정합니다. 바닥이 물체를 받치는 힘을 원한다면 대상과 바닥 사이의 접촉을 골라야 합니다. 위에서 누르는 그리퍼까지 포함하면 물체에 작용한 전체 접촉력의 합이 됩니다. 공식 두 상자 예제에서는 이 구분이 단순하지만, 여러 지지대와 그리퍼가 함께 있는 장면에서는 “지지력”이라는 이름만으로 합산 범위를 정할 수 없습니다.

열 번 기다렸어도 나누는 시간은 마지막 스텝입니다

공식 코드는 dt = 1 / 100.0으로 시간 간격을 정하고 장면을 열 번 진행한 다음 get_contacts()를 호출합니다. 문서는 이 함수가 현재 시뮬레이션 스텝 뒤의 접촉 정보를 가져온다고 설명합니다. 따라서 마지막 스텝의 충격량을 힘으로 바꿀 때 사용하는 시간은 0.01초입니다. 장면을 안정시키려고 먼저 진행한 열 스텝 전체의 0.1초로 나누는 계산이 아닙니다.

앞의 가상 충격량 0.10 N·s가 한 0.01초 스텝에 해당한다면 평균 힘은 10 N입니다. 같은 값을 0.1초로 잘못 나누면 1 N이 됩니다. 그래프 모양이 그럴듯해도 시간 범위를 잘못 연결하면 크기가 열 배 달라지는 셈입니다.

여러 스텝에 걸친 평균을 원한다면 그 구간의 충격량들을 빠짐없이 모아 총 시간으로 나눠야 합니다. 마지막 스텝의 값 하나를 더 긴 구간의 충격량처럼 사용할 수는 없습니다. 렌더링 프레임 간격이나 로그를 저장한 간격도 물리 계산의 dt와 같다고 가정하면 안 됩니다.

이렇게 구한 값은 해당 시간 구간에 걸친 평균 접촉력입니다. 충돌 순간의 최대 힘이나 실제 센서의 고주파 응답을 그대로 복원한 값은 아닙니다. 물리 센서가 무엇을 측정하고 어떤 보정이 필요한지는 로봇 힘·토크 센서 설명에서 별도로 다룹니다.

중력과 비교하는 검사는 두 상자의 정지 조건에 맞춰져 있습니다

공식 예제는 아래쪽 상자를 운동학적 물체로 만들고 위쪽 상자를 동적 물체로 둡니다. 장면을 진행한 뒤 위 상자의 지지력이 9.81 × 질량과 가까운지 확인하는 assertion을 포함하며 상대 허용오차는 1e-3입니다.

이 비교는 위 상자가 안정되어 중력과 위쪽 접촉력이 균형을 이룬다는 상황에 맞춰져 있습니다. 물체가 가속하거나 다른 물체가 누르거나 추가적인 힘을 받는 장면에서는 같은 등식을 그대로 기대할 수 없습니다. 접촉력만 합한 값과 중력까지 포함한 전체 힘도 구분해야 합니다.

접촉 목록의 존재 자체도 하중이 전달됐다는 뜻은 아닙니다. 문서는 두 물체 사이의 거리가 설정된 contact offset보다 작아지면 접촉이 생성될 수 있다고 설명하며, 실제 접촉 반응을 판단할 때는 접촉점의 impulse를 보라고 안내합니다. 목록에 들어왔는지와 얼마의 충격량이 전달됐는지는 서로 다른 정보입니다.

따라서 SAPIEN 접촉값을 힘 그래프로 옮길 때 결과에 함께 붙일 정보는 명확합니다. 어느 물체에 대한 힘인지, 어떤 상대 물체의 접촉을 포함했는지, 부호를 어떻게 맞췄는지, 어느 물리 스텝의 시간 간격으로 나눴는지가 있어야 그 그래프의 크기와 방향을 해석할 수 있습니다.