Pinocchio의 충돌 검사에는 충돌용 형상, 비교할 형상의 쌍, 검사할 현재 자세가 각각 필요합니다. 메시를 읽었다는 사실은 첫 단계에 해당합니다. 어떤 두 형상을 비교할지는 collisionPairs가 정하고, 그 목록을 현재 자세에 놓고 계산한 결과는 GeometryData에서 읽습니다.
이 구분은 화면에 로봇이 정상적으로 보이는데 예상한 충돌이 결과에 나타나지 않을 때 유용합니다. 먼저 형상이 충돌 모델에 들어 있는지, 다음으로 해당 조합이 검사 목록에 남아 있는지, 마지막으로 지금 읽는 결과가 어느 자세의 계산인지 확인할 수 있기 때문입니다.
충돌 형상과 검사 쌍은 별도 입력이다
Pinocchio 공식 충돌 예제는 URDF로 관절 모델을 만든 뒤 buildGeomFromUrdf에 GeometryType.COLLISION을 전달합니다. 여기서 읽는 대상은 충돌 검사에 쓰는 geometry입니다. 시각화용 형상을 잘 표시하는 것과 구분해야 합니다.
이후 예제는 geom_model.addAllCollisionPairs()를 호출합니다. 형상들이 존재하는 단계에서, 그중 무엇을 서로 비교할 것인지 정하는 단계로 넘어가는 것입니다. 이어 SRDF 파일을 읽어 removeCollisionPairs로 제외할 조합을 제거합니다.
| 예제의 단계 | 정해지는 내용 | 결과를 해석할 때의 질문 |
|---|---|---|
| 충돌 geometry 읽기 | 검사에 사용할 형상 | 관심 있는 링크나 물체의 형상이 들어 있는가? |
| collision pair 생성 | 비교할 형상 조합 | 문제의 두 형상이 한 쌍으로 등록됐는가? |
| SRDF에 따른 pair 제거 | 검사 목록에서 제외할 조합 | 그 조합을 왜 제외했는가? |
| 현재 자세에서 검사 | 남은 쌍들의 충돌 결과 | 어떤 q를 대상으로 계산했는가? |
SRDF 제외는 형상을 충돌하지 않는 상태로 이동시키는 연산이 아닙니다. 계산할 조합의 목록을 바꾸는 연산입니다. 따라서 제외된 조합과 검사 결과가 False인 조합은 서로 다른 상태입니다.
검사 쌍을 줄이면 결과의 분모도 달라진다
설명을 위해 형상 A·B·C·D에 대해 처음 만들어진 검사 목록에 여섯 조합이 들어 있다고 가정해 보겠습니다. 이 목록에서 SRDF 설정으로 A–B와 C–D를 제거하면 남는 것은 A–C, A–D, B–C, B–D 네 쌍입니다. 실제 로봇의 쌍 수를 제시한 예가 아니라, 목록 변경의 의미를 보이는 가상 예입니다.
남은 네 결과가 모두 충돌 없음이면 “현재 자세에서 이 네 쌍은 충돌하지 않았다”고 읽을 수 있습니다. A–B와 C–D는 이번 계산의 대상에서 빠졌으므로 결과를 붙일 수 없습니다. 나중에 이 두 조합이 중요해졌다면 pair 목록을 다시 구성해야 합니다.
공식 예제가 pair 생성 직후와 SRDF 제거 직후의 개수를 각각 출력하는 이유도 이 흐름으로 이해할 수 있습니다. 두 숫자는 알고리즘 성능 점수가 아니라 검사 범위가 어떻게 바뀌었는지를 보여 줍니다. 실제로 어떤 조합이 남았는지는 개수와 함께 pair 목록을 읽어야 알 수 있습니다.
목록을 정한 뒤 GeometryData를 만들고 결과를 대응시킨다
공식 예제는 SRDF 필터링을 마친 다음 GeometryData(geom_model)을 만듭니다. 이후 전체 충돌 검사 결과를 읽을 때는 같은 인덱스 k를 사용해 collisionPairs[k]와 collisionResults[k]를 연결합니다.
아래는 그 핵심 호출을 짧게 옮긴 것입니다. model, geom_model, q와 SRDF 경로는 준비된 상태를 전제로 하며, 설치부터 실행까지 포함하는 독립 스크립트는 아닙니다.
geom_model.addAllCollisionPairs()
pin.removeCollisionPairs(model, geom_model, srdf_model_path)
data = model.createData()
geom_data = pin.GeometryData(geom_model)
pin.computeCollisions(model, data, geom_model, geom_data, q, False)
for k, pair in enumerate(geom_model.collisionPairs):
result = geom_data.collisionResults[k]
print(pair.first, pair.second, result.isCollision())
여기서 출력하는 first와 second는 pair가 가리키는 geometry 인덱스입니다. 결과 배열의 몇 번째 값인지만 보관하면 어느 형상 조합의 결과인지 놓치기 쉽습니다. 쌍의 대응 관계를 함께 읽으면 “충돌이 났다”에서 “어느 두 형상이 충돌했는가”로 질문을 좁힐 수 있습니다.
pair 목록을 편집한 뒤 계산을 다시 구성할 때도 이 예제처럼 최종 목록에 맞춰 GeometryData를 만드는 순서를 따르는 것이 명확합니다. 이전 목록의 결과 인덱스를 새 목록의 인덱스로 그대로 해석하면 두 값의 대응을 잘못 읽을 수 있습니다.
단일 pair 검사는 geometry 배치를 먼저 갱신한다
전체 검사 호출에는 q가 들어갑니다. 반면 예제 끝의 computeCollision(geom_model, geom_data, 0)에는 q 인자가 없습니다. 공식 코드는 그 직전에 다음과 같이 geometry 배치를 갱신합니다.
pin.updateGeometryPlacements(model, data, geom_model, geom_data, q)
pin.computeCollision(geom_model, geom_data, 0)
마지막 0은 남아 있는 pair 목록의 첫 번째 항목을 뜻합니다. 특정 링크 번호나 “전체 검사”를 나타내는 값으로 읽으면 안 됩니다.
이 호출 구조를 보면 자세가 바뀐 경우에 무엇을 갱신해야 하는지도 알 수 있습니다. 예를 들어 손목을 회전시켜 새 q를 만들었다면, 단일 pair 검사에 앞서 그 q로 geometry 배치를 갱신해야 합니다. Python 변수 q만 바뀐 상태와 geom_data에 새 배치가 반영된 상태는 구분됩니다.
한 자세의 결과를 경로 검사로 이어갈 때
공식 예제는 Romeo 모델과 SRDF의 half_sitting 기준 자세를 사용합니다. 따라서 그 흐름을 다른 로봇에 적용할 때는 모델·메시 경로뿐 아니라 사용할 SRDF와 기준 configuration도 해당 로봇에 맞게 준비해야 합니다. 이 글은 2026년 10월 16일 확인한 devel 예제 코드를 읽어 설명했으며, 예제 실행이나 충돌 결과 측정은 수행하지 않았습니다.
한 자세의 pair 검사에서 얻는 것은 그 자세와 그 목록에 대한 결과입니다. 이동 경로를 다루려면 경로 위에서 검사할 상태를 어떻게 정하고, 상태 사이 구간을 어떻게 다룰지까지 별도로 결정해야 합니다. 이 다음 단계는 로봇 모션 플래닝과 궤적 최적화의 차이와 연결됩니다. 계획기에 넣기 전, pair 목록과 자세 갱신이 먼저 맞아야 경로상의 검사도 의도한 형상을 대상으로 수행됩니다.