子オブジェクトが目的の姿勢に一致するように親オブジェクトを動かす

エンジニアの岡村です。

xR系のプロダクトやらプロトタイプやらを作っていると、タイトルの様な事をしたいなと思う事がたまにあります。

その時々にコードを適当に書いては「あれ?動かないな?」となる事が多かったので備忘録を残しておくことにしました。

結論

/// <summary>
/// childの座標と回転がtargetPoseと一致するように、parentを移動させます。
/// parentとchildの間に他のTransformがあっても動作しますが、parentとchildは実際に親子関係である必要があります。
/// </summary>
/// <param name="parent">位置合わせの為に動かすオブジェクト</param>
/// <param name="child">位置を合わせるオブジェクト</param>
/// <param name="targetPose">目標となる姿勢(ワールド空間)</param>
private static void AlignChildByMoveParent(Transform parent, Transform child, Pose targetPose)
{
    var rotationDiff = Quaternion.Inverse(child.rotation) * parent.rotation;
    parent.rotation = targetPose.rotation * rotationDiff;
    var positionDiff = parent.position - child.position;
    parent.position = targetPose.position + positionDiff;
}

上記関数に親子関係にあるそれぞれの Transform と、子の位置となる姿勢を渡してやると、 親の位置、回転を合わせてくれます。 この関数ではワールド空間を元に計算している為、親と子の間に他の GameObject が挟まっていても位置合わせ可能です。

ただし、回転による座標変換はTransformの親子処理に頼っているので、parentとchildは実際に親子関係にある必要があります。

やりたいこと

あるオブジェクトAとBがあり、オブジェクトBは親を持つ。

このとき、オブジェクトBの親を動かして、オブジェクトBの座標と回転をオブジェクトAに一致させたい。

図は二次元だが、実際には三次元空間で位置合わせを行う。

解き方

自分自身がクォータニオンを説明できるほど理解できていない為ざっくりとした説明です。

やっていること自体は単純で、親子の差分を取った上で、ターゲットの姿勢に差分を加えて親に適用しているだけです。 ただし、回転を先に算出、適用します。

親の回転が変化すると、子とターゲットの相対位置が変化するので、その相対位置を親に適用することで位置合わせは完了します。

この関数を覚えておくと、ARマーカーを使った位置合わせや、宇宙船のドッキングの様な処理をするときに便利です。