5
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

アンカーに対するオブジェクト相対位置の共有、ARCore Cloud Anchor の下調べ

Last updated at Posted at 2019-07-01

時代はAR!

最近スマートフォンを新しいやつに買い替えたので、ついにおうちでARCoreができるようになりました。
サンプルを一通り触ってみたのですが、目玉機能であるCloud Anchorのサンプルがあまりにもわからないすぎる1ので自分で勉強がてら作り直してみようかと思っています。この記事はその下調べ。
Unityです。

参考サイト

話を始める前に参考になったサイトを貼ります。これを元に話をしています。

前提とか

  • Working with anchors
    anchorとはなんぞや、という説明。
  • Using Instant Preview
    開発効率がぜんぜん違うので、できるようにしておきましょう2。あと2mくらいの長くて頑丈なUSBケーブル。
  • はじめようArcore (修正版)
    まず最初にこれをこなすと公式サンプルへの理解が捗ります。
  • Supported Devices
    どのデバイスで動くか。このページいつも探してる気がするので、ついでにメモとして貼ります。

やりたいこと

ARCore Cloud Anchor APIをマスターする(前編)
ARCore Cloud Anchor APIをマスターする(後編)
どうして空間共有で先に座標系の逆変換(inverse)を行うのか?

Cloud Anchorは実空間上の一点をアンカーと定め、それを共有する仕組みです。
つまり起点であるアンカーとの相対距離・相対角度を求めることができればオブジェクトの位置と向きを共有できるはずです。
CloudAnchorのサンプル内でそれを行っているのは以下のメソッドです。

/// <summary>
/// Converts a pose from Unity world space to Anchor-relative space.
/// </summary>
/// <returns>A pose in Unity world space.</returns>
/// <param name="pose">A pose in Anchor-relative space.</param>
private Pose _WorldToAnchorPose(Pose pose)
{
    if (!m_IsOriginPlaced)
    {
        return pose;
    }

    Matrix4x4 anchorTWorld = Matrix4x4.TRS(
        m_AnchorTransform.position, m_AnchorTransform.rotation, Vector3.one).inverse;

    Vector3 position = anchorTWorld.MultiplyPoint(pose.position);
    Quaternion rotation = pose.rotation * Quaternion.LookRotation(
        anchorTWorld.GetColumn(2), anchorTWorld.GetColumn(1));

    return new Pose(position, rotation);
}

わからん。

PositionRotation

Position

PositionLocalPositionを両方表示する

UnityのInspectorのTransformは親がないときはPositionを表示、親オブジェクトがいる場合はLocalPositionを表示するというなかなか殺意に満ちた仕様をしています。
デバッグ表示にしてすら表示してくれません。なんでそんなひどいことするんでしょうか。謎。
これが見えないと話が始まらないのでInspectorを拡張します。
NGUIのようにTransformを便利に拡張する
元記事のコードにちょっと足してPositionLocalPositionを同時に表示できるようにします。

スクリーンショット 2019-06-27 2.19.04.png

Positionを相対に変換

共有したいオブジェクトをアンカーの子オブジェクトにしてLocalPositionだけ引っこ抜けばいいような気もします。
しますが、PositionLocalPositionを混用すると不思議の森に迷い込むので、わたしは基本的にPositionだけを使うようにしています。なのでPositionだけで相対座標を導出します。

送信側
// 送信側のアンカー
Transform SenderAnchor;
// 共有したい座標
var worldPosition = ts.position;

// アンカーからの相対座標に変換します
var relativePosition = SenderAnchor.transform.worldToLocalMatrix.MultiplyPoint3x4(worldPosition);
受信側
// 受信側のアンカー
Transform ReceiverAnchor;
// 受け取り側のPositionに復元します
var local = ReceiverAnchor.transform.localToWorldMatrix.MultiplyPoint3x4(relativePosition);

変換の仕組みは標準APIの中にありました。メインスレッドからしか実行できません。たぶん。たしか。
別スレッドで行いたい場合は行列を勉強してなんか頑張ってください。

Rotation

わからんことはわかっている3ので考えないで検索します。

【Unity道場 博多スペシャル 2017】クォータニオン完全マスター
【Unity道場スペシャル 2017博多】クォータニオン完全マスター

相対のRotationの出し方は最後のほうにあります。でもすごい勉強になるので最初から見たほうがよいと思います。4

送信側
// 共有したい姿勢
var worldRotation = ts.rotation;
// アンカーからの相対角度に変換します
var relativeRotation = Quaternion.Inverse(SenderAnchor.transform.rotation) * worldRotation;
受信側
// 受け取り側のRotationに復元します
var local = ReceiverAnchor.transform.rotation * relativeRotation;

PositionRotation

完成品はこれ。

public static ValueTuple<Vector3, Quaternion> WorldToRelative(Transform anchor, Vector3 worldPosition, Quaternion worldRotation)
{
    return (anchor.transform.worldToLocalMatrix.MultiplyPoint3x4(worldPosition), Quaternion.Inverse(anchor.rotation) * worldRotation);
}

public static ValueTuple<Vector3, Quaternion> RelativeToWorld(Transform anchor, Vector3 relativePosition, Quaternion relativeRotation)
{
    return (anchor.transform.localToWorldMatrix.MultiplyPoint3x4(relativePosition), anchor.rotation * relativeRotation);
}

実験

必要なコードはできたので適当なコードでテストしてみます。

// 送信側のアンカー
[SerializeField] private GameObject SenderAnchor;
// 送信側の子
[SerializeField] private GameObject SenderChild;
// 受信側のアンカー
[SerializeField] private GameObject ReceiverAnchor;
// 受信側の子
[SerializeField] private GameObject ReceiverChild;


void Start()
{
    var objects = new[] {SenderAnchor, SenderChild, ReceiverAnchor, ReceiverChild};
    foreach (var o in objects)
    {
        // てきとうにカラーリング
        o.GetComponent<Renderer>().material.color = UnityEngine.Random.ColorHSV();
    }

    StartCoroutine(Test());
}


private IEnumerator Test()
{
    var wait = new WaitForSeconds(2);
    while (true)
    {
        // アンカーをランダムな位置に配置
        SenderAnchor.transform.SetPositionAndRotation(UnityEngine.Random.onUnitSphere, UnityEngine.Random.rotation);
        ReceiverAnchor.transform.SetPositionAndRotation(UnityEngine.Random.onUnitSphere + new Vector3(7, 0, 0), UnityEngine.Random.rotation);

        // 送信側の子オブジェクトをランダムな位置に配置
        SenderChild.transform.position = UnityEngine.Random.onUnitSphere * 2;
        SenderChild.transform.rotation = UnityEngine.Random.rotation;

        // 送信側のワールド座標からアンカーからの相対座標に変換
        var ts = SenderChild.transform;
        var (sendP, sendQ) = WorldToRelative(SenderAnchor.transform, ts.position, ts.rotation);

        // アンカーからの相対座標を受信側のアンカーに依るワールド座標に変換
        var (receiveP, receiveQ) = RelativeToWorld(ReceiverAnchor.transform, sendP, sendQ);
        ReceiverChild.transform.SetPositionAndRotation(receiveP, receiveQ);

        yield return wait;

        // アンカー同士の位置を揃えることで子オブジェクトの位置が揃っていることを確認する
        SenderAnchor.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);
        ReceiverAnchor.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity);

        yield return wait;
    }
}

ezgif-2-85609692c59e.gif

このgifではCapsuleをアンカー、Cylinderを共有オブジェクトに設定しています。
目視での確認、および先程拡張したInspectorのTransformで確認します。ちゃんとPositionRotationも一致しているようです。

まとめ

正直Rotationの共有部分は2×2の4パターンを試しただけです。まあなんかあってるし。いいかなって。
……あってると書いておいてなんなのですが、CloudAnchorのサンプルの座標変換ではRotationMatrixを使って変換しているので、もしかしたらこの記事のやり方ではだめなのかも。
というかサンプル内で送られてきた座標を復元しているところも今の所発見できていません。そんなに量はないはずなのに、あのサンプル、なんかほんとよみづらい……。

次は Unity + CloudAnchor + PUN2 を組み合わせてわたしのかんがえたさいこうのCloudAnchorさんぷるを出す予定です。

おしまい。

  1. わからん! Google様のサンプルはわからん!

  2. Instant Preview? AndroidStudioのアレと同レベルでしょ? とか言っててすいませんでした。

  3. 脱線ですが、ソクラテスってあれだけ煽り散らしてたらそら処刑されますよね。

  4. じつはわたしは熊なのでこのような勉強会に行くには差し障りがあるのですが、一度は行ってみたいものです。

5
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?