はじめに
IKを使って歩行アニメーションを行う場合などに、任意形状の地面との距離や接している場所の法線を取得したいことがあります。
そんなときに使える、任意形状のCollider
とSphereCollider
の接触判定を行い、接している場合はSphereCollider
から見て任意形状のCollider
上の1番近い位置とその位置の法線ベクトルなどを返す静的メソッドを紹介します。
実装
PhysicsUtil.cs
using UnityEngine;
public static class PhysicsUtil
{
/// <summary>
/// sphereColliderがtargetColliderに接しているか調べ、接している場合はclosestPosとsurfaceNormalを計算する
/// </summary>
/// <param name="targetCollider">sphereColliderが接しているかを調べる任意のコライダー</param>
/// <param name="sphereCollider">targetColliderに接しているかを調べる球体コライダー</param>
/// <param name="closestPos">
/// targetCollider表面上の、sphereColliderの中心に最も近い点。
/// 接触していない場合はゼロベクトルを返す
/// </param>
/// <param name="surfaceNormal">
/// 接触しているtargetCollider表面の法線ベクトル。
/// 接触していない場合はゼロベクトルを返す
/// </param>
/// <param name="surfacePenetrationDepth">
/// sphereColliderをtargetColliderから引き離すのに必要なsurfaceNormalに沿った距離(どれだけtargetColliderがめり込んでいるか)。
/// 接触していない場合は0を返す
/// </param>
/// <param name="scale">sphereColliderで考慮するスケール</param>
/// <returns>sphereColliderがtargetColliderに接しているか</returns>
public static bool ComputeClosestPosition(Collider targetCollider, SphereCollider sphereCollider,
out Vector3 closestPos, out Vector3 surfaceNormal, out float surfacePenetrationDepth,
float scale = 1f)
{
closestPos = Vector3.zero;
surfaceNormal = Vector3.zero;
surfacePenetrationDepth = 0f;
if (targetCollider == sphereCollider) return false;
Transform targetTrans = targetCollider.transform;
Transform sphereTrans = sphereCollider.transform;
Vector3 spherePos = sphereTrans.position;
bool isOverlap = Physics.ComputePenetration(targetCollider, targetTrans.position, targetTrans.rotation,
sphereCollider, spherePos, Quaternion.identity, out surfaceNormal, out surfacePenetrationDepth);
if (isOverlap)
{
closestPos = spherePos + surfaceNormal * (sphereCollider.radius * scale - surfacePenetrationDepth);
surfaceNormal = -surfaceNormal;
}
return isOverlap;
}
public static bool ComputeClosestPosition(Collider targetCollider, SphereCollider sphereCollider,
out Vector3 closestPos, out Vector3 surfaceNormal)
{
return ComputeClosestPosition(targetCollider, sphereCollider, out closestPos, out surfaceNormal, out _);
}
/// <summary>
/// LayerMaskに指定のLayerが含まれているかを調べる
/// </summary>
/// <param name="layerMask"></param>
/// <param name="layer"></param>
/// <returns></returns>
public static bool Contains(this LayerMask layerMask, int layer)
{
return layerMask == (layerMask | (1 << layer));
}
}