これちのPost-it

技術ネタをペラペラと

絵心皆無のエンジニアの俺がMayaを勉強して3Dモデルを作ってみる【第1回】

はじめに

最近3Dモデルを自分で作れたらなーと思う機会がひじょーーーに増えました。
HoloQuestで使うモンスターに3Dモデルが必要ですね。いつまでもスライムを使っていられません()
あと、最近流行っているバーチャルユーチューバーですね。すごくやってみたいんですが、3Dモデルがどうしても必要に。。
とは言え、AssetStoreからモデルを買ってもオリジナリティを出すのはなかなか難しいし、果たしてそれで良いのか感。
そこで!じゃあもう自分でやれるところまでやってみよう!と思い、今回3Dモデリングを1から勉強することにしました!
コードしか書いたことのない絵心皆無のエンジニアですが()、3Dモデルを1から勉強し始めてどこまで出来るようになるか、チャレンジしたいと思います!目指せバーチャル(イケメン)ユーチューバー!

モデリングソフト

  • Maya 2018
    • 初めは無料体験版を使う。期限が切れたらMayaLTを購入するかも

動作環境

勉強方法

よく分からないので、「Maya チュートリアル」と検索して出てきたMaya Learning Channelの動画を見て勉強します。
これ以降は進捗と、適宜自分用のメモを残すといった感じになります。
あくまで自分の勉強がメインなのでスクリーンショットとかはそんなに多く用意できないと思いますすみません。

1. Autodesk Maya のインタフェース概要

  • Fキーを使って「モデリング」「リギング」「アニメーション」・・・を切り替えできる

ふむふむとチュートリアルを進めていたら動画にはあるのに自分の画面にはツールボックスがない!
調べてもよくわからずいろいろ触っていたら、画面右下のf:id:korechi:20171228170937p:plainボタンを押したらプリファレンスが出てきて、インターフェース->UI要素->ツールボックスにチェックを入れたら無事ツールボックスが出てきた!
f:id:korechi:20171228171017p:plain:h250
こういうチュートリアルと画面が違うってパターンは心臓に悪いな。。
そしたらまた再生コントロール画面も無い。。同じようにツールボックスにてタイムスライダにチェックを入れたら出てきた!ぐぬぬ

2. ビューポートカメラの操作/ビューポートシェーディング

  • ビュー操作
    • 回転:Alt+左ドラッグ
      • Alt+Shiftで固定可
    • 並行移動:Alt+真ん中マウス
    • ズーム:Alt+右ドラッグ or スクロール
      • Alt+Ctrl+左ドラッグで指定区域にズーム
    • 画面フィット:オブジェクトを選択してF(例えば頂点のみ)
      • 全体をフィットさせるにはA
  • ビューを変えるにはパネル->正投影などから選択する
  • シェーディングのショートカット
    • 4: ワイヤーフレーム
    • 5: シェーディング
    • 6: テクスチャ付シェーディング f:id:korechi:20171228173638p:plainこれがそれに対応しているっぽい

3. ホットボックスとマーキングメニュー

Spaceを長押しするとホットボックスが出るらしいがなぜか出てこない・・・
そしたらサブディスプレイにMayaが置いてあるとホットボックスが表示されず、メインディスプレイにMayaが置いてあるとホットボックスが表示されていた・・・何だこの仕様。バグかな?気を取り直して

  • スペースキーを押すとホットボックスが表示され、さらに右ドラッグすると別のものも選択できる
    • 右ドラッグする開始位置によって4種類のショートカットホットボックスが表示される
      • 上:ワークスペースの画面分割
      • 右:インターフェースの表示非表示
      • 下:エディタへ切り替えるメニュー
      • 左:選択コンポーネントのマスクメニュー
      • センターのMaya部:カメラビュー(視点)切り替え
  • 作成項目からオブジェクトが作成できる
  • マーキングメニュー
    • オブジェクトを選択して右長押し
      • ctl+右長押しするとさらにメニューが切り替わる
      • shift+右長押しなどで押し出しとかもできる

4. 選択モードとマニピュレータ

  • f:id:korechi:20171228182331p:plain選択
    • 一括選択:左ドラッグ
    • 選択解除:ctrl押しながら選択
    • 追加選択:shift押して選択
    • 選択切り替え:shift+ドラッグ
  • f:id:korechi:20171228182358p:plain投げ縄選択
  • f:id:korechi:20171228182425p:plainペイント選択
    • ペイント範囲の調整の仕方は分からない・・・次必要になった時に改めて調べよう
  • f:id:korechi:20171228182905p:plainマニピュレータ
    • 移動:W
    • 回転:E
    • スケール:R
    • マニピュレータの大きさはテンキーの「+」「ー」で変更
    • 軸固定移動
      • 軸をドラッグor軸を選択して真ん中マウスをドラッグ
      • shift+真ん中マウスドラッグでも軸固定並行移動
  • ダブルクリックでループ選択

5. コンポーネント操作とスナップ機能

  • ポリゴンメッシュは頂点、エッジ、フェースで構成
    • 右長押しでモード切り替え
  • 頂点情報
  • 法線
    • 法線の向きで裏表が決まる
    • レンダリングする時にサーフェイスから光がどう反射するか計算
      • モデルをつくる時はすべて表が見えるように注意すべし
  • スナップ機能f:id:korechi:20171228184501p:plain
    • グリッドスナップ:最も近いグリッド(格子状)に沿って移動
      • X押しながら
    • ポイントスナップ:最も近いポリゴンの頂点に吸い付くように移動
      • V押しながら
    • カーブスナップ:カーブにそわせてスナップ
      • C押しながら
      • オブジェクトごとカーブにそわせることも可能

おわりに

Maya楽しいです!!
5回分、多分合計30分もない動画しか見ていませんが、分からないことだらけで調べながら進めてたら2時間以上かかりました。
Blenderはあまり自分にあっていなかったのか、3回チャレンジして全部1日で挫折しましたがMayaは続けられそう?かも。
ただ、分からないことがあって調べてもなかなか出てこない。僕の検索の仕方が悪いのもあると思いますが、参考ブログ的なのが少ないのかな。
モデラーさんはエンジニアみたいにブログにちょこちょこまとめることが多くはないのかも?
(MayaLTについて調べていたら、凹みTipsが出てきた時には笑いました)
分からないことがあったら自力で探すしかない。けどそれはそれで楽しい!
次回もちゃんと続きをやりたいと思います!
ではまた〜

iPhone をモーションコントローラとして使えるパッケージを作った【AR/VR向け】

はじめに

HoloQuestでも使用している、iPhoneの動きを別のアプリで取得してiPhoneと同じ位置に何かを表示するパッケージを作りました。
OculusやViveは専用モーションコントローラが存在しますが、HoloLensやGearVRには3次元位置が取得可能な専用モーションコントローラが存在しません。
そこで、こういったデバイスに対して汎用的に3次元位置や傾きを送信するアプリをiPhoneにインストールすることで、iPhoneを疑似モーションコントローラとして使うことが出来ます。

デモ

www.youtube.com

応用例

www.youtube.com

配布先

使用するアプリ

スマートフォン上で動作し、スマートフォンの動きを送信するARMotionControllerと、それを受信するARMotionReceiverを使用します。
ARMotionControllerは多くのアプリに簡単に組み込めるため、お好きなデバイス向けにビルドして使用して下さい。

前提

スマートフォンスマートフォンの位置を送信する相手(PC等)は同じネットワーク環境に繋がっている必要があります
同じwifiを使うなり、テザリングなどを使用して下さい!

使い方

現在ARMotionControllerはまだiPhoneにしか対応していないため、以降はiPhoneを使って説明します。
簡単のため、iPhoneの位置送信相手もPCとします。

手順

  1. iPhoneとPCを同じネットワークに接続
  2. iPhoneにARMotionControllerをインストールし、アプリ起動(Unityを使っても、AppStoreからインストールしてもどちらでも可)
  3. PCのIPアドレスを調べてアプリ内入力欄に記入して待機(まだStartボタンは押さない)
  4. ARMotionReceiver.unitypackageをここからDLし、自分のUnityアプリケーションにインポート
  5. Assets->Prefabs->HandTracker.prefabAssets->Prefabs->RightHandSample.prefabをHierarchyに配置
  6. Run
  7. iPhone側のアプリでStartボタンを押す
  8. 通信開始

今後

ARMotionControllerをARCore向けにも対応

WindowsでARKitRemoteを利用する時の注意

ARKitRemoteは、iOS端末上にARKitRemoteアプリがインストールされていれば基本Windowsからでも利用できます。
ただし、iTunesWindowsマシンに入っている必要があります。

docs.unity3d.com

ここの

iOSバイス (OS XiTUnes がインストールされた Windows とUSB を通して繋がっている iPhoneiPadiPod touchApple TV)

という部分ですね。

ARKit を Unity エディタ上で動作確認出来る、ARKitRemoteについて調査した

ARKitRemoteとは

Unity エディタ上で ARKit アプリの動作確認を行うための、iOS 端末上で動作するアプリです。

背景

本来、Unityで開発しているアプリを iOS 端末上で動作確認するためには、いちいち Unity のアプリを Xcode 用にビルドし、それを Xcode 上でビルドして iOS 端末にインストールする必要があります。
それはめんどくさいです。Unity エディタ上だけで動作確認が出来れば非常に便利ですよね。
しかし、ARKit の特性上 iOS 端末からのカメラ映像やセンサーの値が必要です。
それらを iOS 端末から Unity エディタに送信して、Unity 側でよしなに受け取った値をもとに動作をエミュレートさせよう、というのが ARKitRemote シーンの役割です。

動作するアプリ

なので、まずは大きく2つのアプリが動作することになります。

  1. iOS 上で動作する、カメラ映像などを Unity エディタに送信するアプリ
    • ARKitRemoteが動作
    • UnityARKitPlugin -> ARKitRemote -> ARKitRemote.Unityのシーンそのもの
    • 以降リモートアプリと呼ぶ
  2. Unity エディタ上で動作するアプリ
    • 本来作成したいアプリ
    • 以降Myアプリと呼ぶ

動作環境

  • Unity 2017.2.0f3
  • iPhone6s plus (iOS11.2)
  • Xcode 9.2

使い方

まずUnityRemoteKitの使い方は以下のサイトが参考になるので、読んで使ってみてください。
lilea.net
本記事では、ARKitRemote(と、Myアプリに何が必要か)について調査したいと思います。

ARKitRemote シーン

まず中のシーンを開いてみますと以下のようになっており、Main CameraとDirectional LightしかHierarchyにないことが分かると思います。
f:id:korechi:20171205175415p:plain
そして、この Main Camera にアタッチされている、ConnectToEditor.csUnityRemoteVideo.csが ARKitRemote シーンのみに存在していることが他サンプルシーンと比較すると分かります。
(UnityARVideo.cs は他の ARKit サンプルシーンでも使われているスクリプトです)
そのため、上記の2スクリプトを見ていきます。

ConnectToEditor.cs

大まかに言うとこのクラスは、リモートデバイスでの変化(フレームの更新やAnchorの更新)のタイミングのたびに情報を Myアプリ(Editor上で本来動作してる)に送信するクラスです。

PlayerConnection playerConnection;

void Start()
{
        Debug.Log("STARTING ConnectToEditor");
        editorID = -1;
        playerConnection = PlayerConnection.instance;
        playerConnection.RegisterConnection(EditorConnected);
        playerConnection.RegisterDisconnection(EditorDisconnected);
        playerConnection.Register(ConnectionMessageIds.fromEditorARKitSessionMsgId, HandleEditorMessage);
        m_session = null;
}

Start() 時に PlayerConnection インスタンスを生成しています。
これはエディタ、プレイヤー間の接続を扱うクラスです。 この接続はプロファイラとプレイヤーを接続することで確立されます。
インスタンス生成後、接続開始時・終了時のコールバックを設定し、大事なのはその後の処理。
playerConnection.Register(ConnectionMessageIds.fromEditorARKitSessionMsgId, HandleEditorMessage);の第1引数にはConnectionMessageIds.fromEditorARKitSessionMsgIdというのが含まれており、これはARKitRemote.unityと同じ階層にあるConnectionMessageIds.csにIDが設定されているのが確認できます。
このIDは、当然Myアプリ側から同じ値をリモートアプリに接続要求する際に送信されます。(詳しくは、ARKitRemoteConnection.cs参照)
第2引数には、コールバック関数が指定されるため、メッセージが届くたびに HandleEditorMessage () が呼ばれます。

void HandleEditorMessage(MessageEventArgs mea)
{
        serializableFromEditorMessage sfem = mea.data.Deserialize<serializableFromEditorMessage>();
        if (sfem != null && sfem.subMessageId == SubMessageIds.editorInitARKit) {
            InitializeARKit ( sfem.arkitConfigMsg );
        }
}

HandleEditorMessage 内では、メッセージをデシリアライズし、初期化関数が呼ばれます。

UnityARSessionNativeInterface m_session;

void InitializeARKit(serializableARKitInit sai)
{
        #if !UNITY_EDITOR

        //get the config and runoption from editor and use them to initialize arkit on device
        Application.targetFrameRate = 60;
        m_session = UnityARSessionNativeInterface.GetARSessionNativeInterface();
        ARKitWorldTrackingSessionConfiguration config = sai.config;
        UnityARSessionRunOption runOptions = sai.runOption;
        m_session.RunWithConfigAndOptions(config, runOptions);

        UnityARSessionNativeInterface.ARFrameUpdatedEvent += ARFrameUpdated;
        UnityARSessionNativeInterface.ARAnchorAddedEvent += ARAnchorAdded;
        UnityARSessionNativeInterface.ARAnchorUpdatedEvent += ARAnchorUpdated;
        UnityARSessionNativeInterface.ARAnchorRemovedEvent += ARAnchorRemoved;

        #endif
}

前半ではARKitに関する初期化を行い、後半ではフレームの更新や平面の検知・更新・削除の際のイベントに関数を追加しています。UnityARSessionNativeInterfaceクラスの中身の一部は以下のようになっています。

// UnityARSessionNativeInterface.cs

public class UnityARSessionNativeInterface {
//      public delegate void ARFrameUpdate(UnityARMatrix4x4 cameraPos, UnityARMatrix4x4 projection);
//        public static event ARFrameUpdate ARFrameUpdatedEvent;
    
    // Plane Anchors
    public delegate void ARFrameUpdate(UnityARCamera camera);
    public static event ARFrameUpdate ARFrameUpdatedEvent;

ARKitでは水平面を自動で認識しそこにARAnchorを設置してくれるようになっています。
Anchors 以外のイベント(例えばARFaceAnchorAdded)もとれるみたいですが、今回は使いません。(FaceID関係かな?)
あとは、イベントが発生したタイミングで MyアプリにplayerConnection.Send()を使って送信しています。

public void ARFrameUpdated(UnityARCamera camera)
{
    serializableUnityARCamera serARCamera = camera;
    SendToEditor(ConnectionMessageIds.updateCameraFrameMsgId, serARCamera);
}

public void ARAnchorAdded(ARPlaneAnchor planeAnchor)
{
    serializableUnityARPlaneAnchor serPlaneAnchor = planeAnchor;
    SendToEditor (ConnectionMessageIds.addPlaneAnchorMsgeId, serPlaneAnchor);
}

public void SendToEditor(System.Guid msgId, object serializableObject)
{
    byte[] arrayToSend = serializableObject.SerializeToByteArray ();
    SendToEditor (msgId, arrayToSend);
}

public void SendToEditor(System.Guid msgId, byte[] data)
{
    if (playerConnection.isConnected)
    {
        playerConnection.Send(msgId, data);
    }
}

送っている情報は、ARFrameUpdated()では serializableUnityARCamera のインスタンス、ARAnchorAdded()(と書いていないがARAnchorUpdated()、ARAnchorRemoved())ではserializableUnityARPlaneAnchor のインスタンスが送られる。何が送られているのかちょっと覗いてみると

// ARKitRemote -> SerializableObjects.cs

    [Serializable]  
    public class serializableUnityARCamera
    {
        public serializableUnityARMatrix4x4 worldTransform;
        public serializableUnityARMatrix4x4 projectionMatrix;
        public ARTrackingState trackingState;
        public ARTrackingStateReason trackingReason;
        public UnityVideoParams videoParams;
        public serializableUnityARLightData lightData;
        public serializablePointCloud pointCloud;
        public serializableUnityARMatrix4x4 displayTransform;
 [Serializable]  
    public class serializableUnityARPlaneAnchor
    {
        public serializableUnityARMatrix4x4 worldTransform;
        public SerializableVector4 center;
        public SerializableVector4 extent;
        public ARPlaneAnchorAlignment planeAlignment;
        public byte[] identifierStr;

serializableUnityARMatrix4x4というのはMatrix4x4クラスのようなものです。
全部の変数の型を調べてはいませんが、名前からなんとなく送られているデータを察することはできます。
pointCloud 変数には特徴点が含まれているのでしょうね

UnityRemoteVideo.cs

    public ConnectToEditor connectToEditor;
    private UnityARSessionNativeInterface m_Session;

    public void OnPreRender()
    {
        ARTextureHandles handles = m_Session.GetARVideoTextureHandles();
        if (handles.textureY == System.IntPtr.Zero || handles.textureCbCr == System.IntPtr.Zero)
        {
            return;
        }

        if (!bTexturesInitialized)
            return;
        
        currentFrameIndex = (currentFrameIndex + 1) % 2;

        Resolution currentResolution = Screen.currentResolution;

        m_Session.SetCapturePixelData (true, PinByteArray(ref m_pinnedYArray,YByteArrayForFrame(currentFrameIndex)), PinByteArray(ref m_pinnedUVArray,UVByteArrayForFrame(currentFrameIndex)));

        connectToEditor.SendToEditor (ConnectionMessageIds.screenCaptureYMsgId, YByteArrayForFrame(1-currentFrameIndex));
        connectToEditor.SendToEditor (ConnectionMessageIds.screenCaptureUVMsgId, UVByteArrayForFrame(1-currentFrameIndex));         
    }

UnityRemoteVideo.csクラス内では、OnPreRender() が呼ばれており、これはカメラがシーンのレンダリングを開始する前に呼び出されます。関数の実行順はここを参考。
最終的に、connectToEditor.SendToEditor() でbyte[] を送信しています。
詳しくないですが、ここで送信されている Y やら UV は、色空間を表すものらしいので、おそらくこれがビデオ映像の一部ではないかと思われます。

Unity エディタ上で動作するアプリ

ふぅ長くなりましたね。次は、Myアプリ上での処理について見ていきましょう。
今回使用するのはUnityARKitPlugin -> ARKitRemote -> EditorTestScene.unityです。
まずは、このシーン内で他の ARKit サンプルシーンと何が違うか見てみましょう。
f:id:korechi:20171205195956p:plain
ARKitRemoteConnection というプレハブと、HitCubeにEditorHitTest.csがアタッチされているのが分かると思います。それぞれ見ていきましょう。

ARKitRemoteConnection.cs

ARKitRemoteConnection プレハブは、ARKitRemoteConnection.cs がアタッチされているだけのオブジェクトです。
まずは、Start() の中の一部を見てみましょう。

// Start(): 
    editorConnection = EditorConnection.instance;
    editorConnection.Initialize ();
    editorConnection.RegisterConnection (PlayerConnected);
    editorConnection.RegisterDisconnection (PlayerDisconnected);

    editorConnection.Register (ConnectionMessageIds.updateCameraFrameMsgId, UpdateCameraFrame);
    editorConnection.Register (ConnectionMessageIds.addPlaneAnchorMsgeId, AddPlaneAnchor);
    editorConnection.Register (ConnectionMessageIds.updatePlaneAnchorMsgeId, UpdatePlaneAnchor);
    editorConnection.Register (ConnectionMessageIds.removePlaneAnchorMsgeId, RemovePlaneAnchor);
    editorConnection.Register (ConnectionMessageIds.screenCaptureYMsgId, ReceiveRemoteScreenYTex);
    editorConnection.Register (ConnectionMessageIds.screenCaptureUVMsgId, ReceiveRemoteScreenUVTex);

EditorConnection のインスタンスを取得し、コールバック関数を複数設定しています。
EditorConnection はEditor側からプレイヤー側に接続要求をするクラスです。
AddPlaneAnchor() を見てみましょう。

void AddPlaneAnchor(MessageEventArgs mea)
{
    serializableUnityARPlaneAnchor serPlaneAnchor = mea.data.Deserialize<serializableUnityARPlaneAnchor> ();

    ARPlaneAnchor arPlaneAnchor = serPlaneAnchor;
    UnityARSessionNativeInterface.RunAddAnchorCallbacks (arPlaneAnchor);
}

メッセージをデシリアライズし、それをUnityARSessionNativeInterfaceのコールバック関数に登録しています。
こうすることで、カメラフレームの更新があるたびにそれがそのままUnityARSessionNativeInterfaceに送られます

void ReceiveRemoteScreenYTex(MessageEventArgs mea)
{
    if (!bTexturesInitialized)
        return;
    remoteScreenYTex.LoadRawTextureData(mea.data);
    remoteScreenYTex.Apply ();
    UnityARVideo arVideo = Camera.main.GetComponent<UnityARVideo>();
    if (arVideo) {
        arVideo.SetYTexure(remoteScreenYTex);
    }

}

こっちはテクスチャの方で、最後のarVideo.SetYTexure(remoteScreenYTex);いかにもリモートアプリから送られてきたテクスチャを Editor 上に表示させているっぽいですね。(詳しくは分かりませんがおそらくそう)
と、まぁ大まかに、
- Editor側からプレイヤー側に接続要求 - リモートアプリから受信した情報をUnityARSessionNativeInterfaceに送る

でいいのかな?(ちょっと自信ない)

EditorHitTest.cs

こっちは非常に単純です。
Editor上で疑似タップ操作を行えるように、Input.GetMouseButtonDown を Update 関数の中で呼んでいるだけです。

#if UNITY_EDITOR   //we will only use this script on the editor side, though there is nothing that would prevent it from working on device
void Update () {
    if (Input.GetMouseButtonDown (0)) {
        Ray ray = Camera.main.ScreenPointToRay (Input.mousePosition);
        RaycastHit hit;

        //we'll try to hit one of the plane collider gameobjects that were generated by the plugin
        //effectively similar to calling HitTest with ARHitTestResultType.ARHitTestResultTypeExistingPlaneUsingExtent
        if (Physics.Raycast (ray, out hit, maxRayDistance, collisionLayerMask)) {
            //we're going to get the position from the contact point
            m_HitTransform.position = hit.point;
            Debug.Log (string.Format ("x:{0:0.######} y:{1:0.######} z:{2:0.######}", m_HitTransform.position.x, m_HitTransform.position.y, m_HitTransform.position.z));

            //and the rotation from the transform of the plane collider
            m_HitTransform.rotation = hit.transform.rotation;

つまり、この関数がないと Editor 上でタップができなくなるので、ただ眺めるだけのアプリになります。笑

自分のアプリをEditor上で動作確認するためには

  1. ARKitRemoteConnection プレハブをヒエラルキーに配置
    • これだけでも見た目は確認可能
  2. EditorHitTest.cs のような Input.GetMouseButtonDown (0) が書かれたスクリプトをどこかに記述する

HoloLens の Spatial Understanding について調べてみた

参考URL

github.com

Spatial Understanding

Spatial mapping のメッシュ上にオブジェクトを置くとき、床・天井・壁の認識が必要となる。加えて、ホログラフィックオブジェクトの最も望ましい物理的な位置を決定するために、配置制約をもとに最適化することも必要となる。
- これち:この配置制約というのはおそらくSpatialMappingで取得したメッシュ内で、という制約
この課題を解決するため room solver を開発した。これらのコア技術は HoloToolkit.SpatialUnderstanding ライブラリ内に存在しこれを使うことで以下のことが可能となる。
1. 壁の空いたスペースを探す 2. 天井にオブジェクトを配置 3. キャラクターが座ることのできる場所の特定 4. その他の空間クエリ
これらのコードはソースコード内にあるためカスタマイズが可能である。また、C++コードは UWP の dll や Unity 内のプレハブにラップされている。

Ray Casting

部屋のスキャンが進むと床・天井・壁のラベルが生成される。PlayspaceRaycast関数はRayを飛ばし何かの表面にあたったら、RaycastResultクラスにそのあたった場所の情報をのせて知らせてくれる。

struct RaycastResult
{
    enum SurfaceTypes
    {
        Invalid,    // No intersection
        Other,
        Floor,
        FloorLike,  // Not part of the floor topology, 
                    //  but close to the floor and looks like the floor
        Platform,   // Horizontal platform between the ground and 
                    //  the ceiling
        Ceiling,
        WallExternal,
        WallLike,   // Not part of the external wall surface, 
                    //  but vertical surface that looks like a 
                    //  wall structure
    };https://camo.githubusercontent.com/badbca8d962fe99d35fc861aade93fbb8dcc9072/68747470733a2f2f617a3833353932372e766f2e6d7365636e642e6e65742f73697465732f6d697865642d7265616c6974792f5265736f75726365732f696d616765732f726179636173742d726573756c742e6a7067
    SurfaceTypes SurfaceType;
    float SurfaceArea;  // Zero if unknown 
                        //  (i.e. if not part of the topology analysis)
    DirectX::XMFLOAT3 IntersectPoint;
    DirectX::XMFLOAT3 IntersectNormal;
};

Unityサンプルでは、カーソルがRayを飛ばし、Unityコライダーに衝突後、understandingモジュールを介し、UIエレメントに表示される。
f:id:korechi:20171118081440j:plain
SpaceVisualizer.csに使っているクエリなどが書かれているため、参考にするとよい。

Shape Queries

dll内のShapeAnalyzer_Wにてユーザが定義したカスタムshapeとのマッチが行われる。Unityサンプルではサンプルのshapeを定義し、結果をアプリ内のクエリメニューで表示している。このカスタムshapeは水平面でしか機能しないことに注意すべき。
例えばソファーは、平らな座面と平らな頂部によって決定される。以下はUnityで使われるクエリ例である。

shapeComponents = new List<ShapeComponent>()
{
    new ShapeComponent(
        new List<ShapeComponentConstraint>()
        {
            ShapeComponentConstraint.Create_SurfaceHeight_Between(0.2f, 0.6f),
            ShapeComponentConstraint.Create_SurfaceCount_Min(1),
            ShapeComponentConstraint.Create_SurfaceArea_Min(0.035f),
        }
    ),
};
AddShape("Sittable", shapeComponents);
shapeConstraints = new List<ShapeConstraint>()
{
    ShapeConstraint.Create_RectanglesSameLength(0, 1, 0.6f),
    ShapeConstraint.Create_RectanglesParallel(0, 1),
    ShapeConstraint.Create_RectanglesAligned(0, 1, 0.3f),
    ShapeConstraint.Create_AtBackOf(1, 0),
};

f:id:korechi:20171118083035p:plain
SpatialUnderstandingDll.csを読めばより詳しいことがわかる。

Object Placement Solver

object placement solver はオブジェクトルールと制約をもとに、オブジェクトを配置する理想的な位置を探すことができる。オブジェクトのクエリはSolver_RemoveObjectで削除されるまで持続する。
オブジェクト配置クエリは3つのパートからなる。
1. placement type 2. ルールのリスト 3. 制約のリスト これらのクエリを回すためのAPIは以下のようになる。

public static int Solver_PlaceObject(
            [In] string objectName,
            [In] IntPtr placementDefinition,        // ObjectPlacementDefinition
            [In] int placementRuleCount,
            [In] IntPtr placementRules,             // ObjectPlacementRule
            [In] int constraintCount,
            [In] IntPtr placementConstraints,       // ObjectPlacementConstraint
            [Out] IntPtr placementResult)

placement typeは以下のenumで定義される。

public enum PlacementType
            {
                Place_OnFloor,
                Place_OnWall,
                Place_OnCeiling,
                Place_OnShape,
                Place_OnEdge,
                Place_OnFloorAndCeiling,
                Place_RandomInAir,
                Place_InMidAir,
                Place_UnderFurnitureEdge,
            };

ObjectPlacementDefinition構造体はこれらの定義を作成するのを手助けする関数を持っており、以下のように使用することが可能である。

 public static ObjectPlacementDefinition Create_OnFloor(Vector3 halfDims)

また、ルール、制約のリスト作成にもヘルパークラスが存在し以下のように使用することが可能である。

public static ObjectPlacementRule Create_AwayFromPosition(
    Vector3 position, float minDistance)
public static ObjectPlacementConstraint Create_NearPoint(
    Vector3 position, float minDistance = 0.0f, float maxDistance = 0.0f)

f:id:korechi:20171118103804p:plain
上画像のように床の上にオブジェクトを置きたい場合のサンプルコードは以下のように書くことができる。

List<ObjectPlacementRule> rules = 
    new List<ObjectPlacementRule>() {
        ObjectPlacementRule.Create_AwayFromOtherObjects(1.0f),
    };

List<ObjectPlacementConstraint> constraints = 
    new List<ObjectPlacementConstraint> {
        ObjectPlacementConstraint.Create_NearCenter(),
    };

Solver_PlaceObject(
    “MyCustomObject”,
    new ObjectPlacementDefinition.Create_OnEdge(
        new Vector3(0.25f, 0.25f, 0.25f), 
        new Vector3(0.25f, 0.25f, 0.25f)),
    rules.Count,
    UnderstandingDLL.PinObject(rules.ToArray()),
    constraints.Count,
    UnderstandingDLL.PinObject(constraints.ToArray()),
    UnderstandingDLL.GetStaticObjectPlacementResultPtr());

成功したら、ObjectPlacementResult構造体に、置く場所・次元・角度が返却される。
LevelSolver.csファイルを読めば様々なクエリを確認することができる。

Mac で HoloLens 用 UWP アプリをビルドする【Boot Camp】

はじめに

HoloLens アプリ開発は基本的に WindowsPC を使って行います。
しかし今回 MacBoot Camp を使って HoloLens 用アプリを開発してみました。
(探したところ Boot Camp での開発環境構築のサイトが見当たらなかったため)
結論から言えば、問題なくビルドできました!
一応手順を紹介したいと思います。自分へのメモも含めて。

動作環境

  • MacBookPro 13-inch, 2017, Four Thunderbolt 3 Ports

使用するソフトウェア

手順

BootCamp を使って Windows をインストール

  1. Windows 10インストール用の iso ファイルをダウンロード ただし iso が入った USB メディアを買ってある人はこの手順は必要ありません。
  2. Boot Camp を使って Windows10 をインストール
    ちなみに自分はWindows起動後にbootcampコントローラのインストールが失敗し wifi やら右クリックが使えなくなっていたため、以下のリンクから Windows サポートソフトウェアをインストールしなおしました。
    support.apple.com
    Windows サポートソフトウェアをインストールしたら無事に wifi が繋がりました。
    また、Windowsパーティション最低 60GB は必要です。
    ここを節約してしまうと後々困りますので注意してください。
    Windows そのものに数十GB、Visual Studio にも約 20GB 使います。
    自分は、Mac に 150GB、Windows に 100GB パーティションを区切りました。

開発者モードにしておく

Windows側、HoloLens側どちらも開発者モードにしましょう。

HoloLens のサンプルアプリをビルド

  1. HoloToolKit をダウンロードし Unity のプロジェクトに配置
    DLはこちら
  2. 適当なサンプルシーンを開き、UWP 向けにビルド Windows10 SDK がインストールされていないとビルドが通りませんので注意。
    しかし、以下のようなエラーが出ました。
Exception: Failed to locate env variables VS140COMNTOOLS or VS120COMNTOOLS.
Utility.GetVSVersion () (at C:/buildslave/unity/build/PlatformDependent/WinRT/SharedSources/CSharp/Utility.cs:623)
MetroCSharpVisualStudioSolutionCreator.WriteSolutionFile (System.String solutionFileName, UnityEditor.Scripting.ScriptCompilation.ScriptAssembly[] csharpAssemblies) (at C:/buildslave/unity/build/PlatformDependent/MetroPlayer/Extensions/Managed/MetroCSharpVisualStudioSolutionCreator.cs:196)
MetroCSharpVisualStudioSolutionCreator.CreateSolutionFileFrom () (at C:/buildslave/unity/build/PlatformDependent/MetroPlayer/Extensions/Managed/MetroCSharpVisualStudioSolutionCreator.cs:724)
PostProcessWSA.CreateVisualStudioSolution () (at C:/buildslave/unity/build/PlatformDependent/MetroPlayer/Extensions/Managed/PostProcessWSA.cs:320)
PostProcessWinRT.Process () (at C:/buildslave/unity/build/PlatformDependent/WinRT/SharedSources/CSharp/PostProcessWinRT.cs:237)
UnityEditor.WSA.BuildPostprocessor.DoPostProcess (BuildPostProcessArgs args) (at C:/buildslave/unity/build/PlatformDependent/MetroPlayer/Extensions/Managed/ExtensionModule.cs:142)
UnityEditor.WSA.BuildPostprocessor.PostProcess (BuildPostProcessArgs args) (at C:/buildslave/unity/build/PlatformDependent/MetroPlayer/Extensions/Managed/ExtensionModule.cs:149)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)

これはVisual Studio 側の環境が整っていないのが問題らしいので次のステップに進みます。

Visual Studio 側の設定

  1. Visual Studio を起動
  2. ツール -> ツールと機能を取得 f:id:korechi:20171106074156p:plain
  3. ユニバーサル Windows プラットフォーム開発をインストール
    f:id:korechi:20171106074300p:plain

再度ビルド

無事 Unity でビルドが通り、Visual Studio 用ソリューションファイルが生成されました!
あとは Visual Studioコンパイルすれば HoloLens 上でアプリが動作します!

その他

リモートデスクトップ

HoloLens から リモートデスクトップアプリを使って BootCamp で起動した Windows10 に普通にリモートデスクトップ繋がりました。
f:id:korechi:20171105161143j:plain

Unity で作った ARKit 向けアプリをリリースする時に気をつけること【審査まで】

はじめに

今回 ARKit の機能を利用するアプリを Unity を使って作成し、ストアに申請しました。(まだ承認されていないのでストアには並んでいませんが)
そこで、Unity を使って ARKit の機能を利用するアプリをストアに申請する際に出たいろいろなエラーなどを紹介し、回避法やおすすめ設定をメモしておきます。
審査結果が届き次第、またフィードバックを記事にまとめたいと思います。

Unity 側

minimum iOS のバージョンを 11 に

ARKit は iOS 11.0 以降でかつ、iPhone6s 以降に発売された iPhone および iPad でしか動作しません。
そのため、File -> Build Setting -> Other Settings の Target minimum iOS Version を 11.0 にしましょう。
f:id:korechi:20171031200051p:plain:h600

必要ならターゲットデバイスiPhone のみに

もし必要ならですが、上と同じく File -> Build Setting -> Other Settings の Target Device を iPhone Only にしておきましょう。
ただ、iPad でも機種によっては ARKit が動作するため、iPad にも対応させている場合は Target Device を iPhone & iPad にしましょう。

画面回転をしない設定にしておく

これはもちろん作るアプリケーションにもよりますが、自分が今回作ったアプリでは画面回転をしない方が良かったため、このような設定にしました。
File -> Build Setting -> Other Settings の Default Orientation を Portrait にすることで、画面を縦固定にすることができます。
f:id:korechi:20171031201120p:plain:h300

XCode

こちらが意外とネック。
Unity で Xcode 用ソリューションにできたとしても安心できません。
自分は結構ビルドが通らなかったです。

Architectures を arm64 のみに(重要)

Build Setting の Architectures で対象とするデバイスアーキテクチャが選択できます。
これがデフォルトですとおそらく、arm64 armv7 が選択されているのですが、これだとビルドが通りません。以下のようなエラーが出ます。

clang: error: invalid iOS deployment version '-miphoneos-version-min=11.0', iOS 10 is the maximum deployment target for 32-bit targets [-Winvalid-ios-deployment-target]

これは、ターゲットとする iOS のバージョンを 11 以上に設定しているのに、32-bit アーキテクチャは最大で iOS10 ですよーとのエラーです。
こちらの iOSバイス一覧から armv7 は iOS11 に出来ないことがわかります。
qiita.com
そのため、arm64 armv7 の部分を選択し、Othersを選択。
f:id:korechi:20171031202120p:plain
その後、armv7 を削除しましょう。 f:id:korechi:20171031202152p:plain

1024×1024pt のアイコンを設定しておく

ARKit には関係しませんが、App Store にアプリが並ぶ時に表示される 1024*1024pt のアイコンを XCode 上で予め登録しておかないと、iTunes Connect でアプリを審査に出す時にエラーとなってしまいます。
あとでエラーになると面倒なので、予め設定しておきましょう。
1. General -> App Icons Source の右側にある矢印をクリックし、一番下にある欄にアイコンを設定しましょう。
f:id:korechi:20171031202910p:plain:w600

アイコンは非透過の png を用意

これも ARKit には関係しませんが、アイコンは非透過のものを用意しておかないと iTunes Connect でアイコンを設定することができません。
自分は、png(透過)→jpg(非透過)→png(非透過)にして非透過画像の png を用意しました。(もっとスマートなやり方は絶対ある)

とりあえずはざっとこんなところです。
アプリの申請の流れは以下のサイトが参考になりました。
i-app-tec.com