MRが楽しい

MRやVRについて学習したことを書き残す

HoloLens2でホロモンアプリを作る その60(コライダーの上半分だけで撫でる判定を行う)

本日はアプリ作成枠です。
HoloLens2でホロモンアプリを作る進捗を書き留めていきます。
f:id:bluebirdofoz:20210913061816j:plain

今回はコライダーの上半分だけで撫でる判定を行うメモです。

トランスフォームの特定ベクトルと角度

今回はトランスフォームの上方向を示す Vector3.up と Vector3.Angle を利用してコライダーの上半分を判定しました。
docs.unity3d.com
docs.unity3d.com

// 上方方向ベクトルを取得する
Vector3 upDirection = p_TactileRoot.up;

float angle = Vector3.Angle(upDirection, betweenDirection);

// オブジェクトとの方向ベクトルの角度差(0°~ 180°)を取得する
if (Vector3.Angle(upDirection, betweenDirection) > 90)
{
    // 方向範囲外なら触れていない
    return false;
}

実装コード

コライダー全体または上方、前方の衝突オブジェクトを検出するスクリプトを作成しました。
・StandSpawnController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;

using HoloMonApp.ConditionSpace;

// コライダー処理のため
using UniRx;
using UniRx.Triggers;

// エディター起動時判定のため
using UnityEditor;

// CoreSystemへのアクセスのため
using Microsoft.MixedReality.Toolkit;
// 空間認識情報の取得のため
using Microsoft.MixedReality.Toolkit.SpatialAwareness;

namespace HoloMonApp.TactileBodySpace
{
    [RequireComponent(typeof(SphereCollider))]
    public class TactileBodyRegister : MonoBehaviour
    {
        /// <summary>
        /// チェック範囲
        /// </summary>
        private enum CheckRange
        {
            /// <summary>
            /// 全方向
            /// </summary>
            AllRange,
            /// <summary>
            /// 上方向
            /// </summary>
            UpRange,
            /// <summary>
            /// 前方方向
            /// </summary>
            FrontRange,
        }

        /// <summary>
        /// 範囲内の触覚オブジェクトコレクション
        /// </summary>
        [SerializeField, Tooltip("範囲内の触覚オブジェクトコレクション")]
        private TactileBodyCollection p_TactileBodyCollection;


        [SerializeField, Tooltip("触覚原点トランスフォーム")]
        private Transform p_TactileRoot;

        [SerializeField, Tooltip("空間認識レイヤー")]
        private int p_SpatialAwarenessLayer;

        [SerializeField, Tooltip("チェック対象範囲")]
        private CheckRange p_TargetCheckRange;

        [SerializeField, Tooltip("触覚発見距離(ホロモン1m時のメートル単位)")]
        private float p_ForcedDiscoveryDistance = 0.5f;


        /// <summary>
        /// 起動処理
        /// </summary>
        void Start()
        {
            // 空間認識のオブザーバを取得する
            IMixedRealitySpatialAwarenessMeshObserver SpatialAwarenessMeshObserver =
                CoreServices.GetSpatialAwarenessSystemDataProvider<IMixedRealitySpatialAwarenessMeshObserver>();

            // オブザーバからレイヤー番号を取得する
            p_SpatialAwarenessLayer = SpatialAwarenessMeshObserver.MeshPhysicsLayer;

            // コライダーの OnTriggerStay イベントに対する処理を定義する
            this.OnTriggerStayAsObservable()                    // OnTriggerStayイベント
                .BatchFrame()                                   // フレーム毎に値をまとめる
                .ObserveOnMainThread()                          // メインスレッドで行う
                .Subscribe(colliderlist =>
                {
                    // 更新があったか
                    bool isUpdate = false;

                    // 検出したオブジェクトのリスト
                    List<GameObject> objectList = new List<GameObject>();

                    foreach (Collider collider in colliderlist)
                    {
                        if (collider == null) continue;

                        GameObject stayObject = collider.gameObject;

                        if (stayObject)
                        {
                            bool isSeen = TouchTactileObject(stayObject);

                            if (isSeen)
                            {
                                // 検出オブジェクトをコレクションに登録する
                                bool isRegisted =  RegistTactileObject(stayObject);

                                if (isRegisted)
                                {
                                    // オブジェクトを記録する
                                    objectList.Add(stayObject);
                                }

                                // 更新を行ったか
                                isUpdate = isUpdate || isRegisted;
                            }
                        }
                    }

                    // 未検出オブジェクトをコレクションから除去する
                    bool isRemove = RemoveTactileObject(objectList);

                    // 更新を行ったか
                    isUpdate = isUpdate || isRemove;
                })
                .AddTo(this);
        }

        /// <summary>
        /// 定期処理
        /// </summary>
        void Update()
        {
            // 視認オブジェクトの状態更新をチェックする
            p_TactileBodyCollection.CheckStatusUpdate();
        }



        /// <summary>
        /// ゲームオブジェクトが触れているか判定する
        /// </summary>
        private bool TouchTactileObject(GameObject a_TactileObject)
        {
            // オブジェクトのレイヤー番号を取得する
            int layernumber = a_TactileObject.layer;

            if (layernumber == p_SpatialAwarenessLayer)
            {
                // 空間認識レイヤーの場合は無視する
                return false;
            }

            // 触覚原点とオブジェクト間のベクトルを算出し、距離と方向ベクトルを取得する
            Vector3 betweenVector = a_TactileObject.transform.position - p_TactileRoot.position;
            float betweenDistance = betweenVector.magnitude;
            Vector3 betweenDirection = betweenVector.normalized;

            // 距離チェック
            // 強制発見距離を現在のスケールを基に計算する
            float forcedDiscoveryDistance = p_ForcedDiscoveryDistance * CurrentHeightScale();

            // 強制発見距離か否か
            if (betweenDistance > forcedDiscoveryDistance)
            {
                // 強制発見距離外なら触れていない
                return false;
            }

            // 方向チェック
            switch (p_TargetCheckRange)
            {
                case CheckRange.AllRange:
                    // 全方向なら方向チェックはしない
                    break;
                case CheckRange.UpRange:
                    // 上方方向ベクトルを取得する
                    Vector3 upDirection = p_TactileRoot.up;

                    float angle = Vector3.Angle(upDirection, betweenDirection);

                    // オブジェクトとの方向ベクトルの角度差(0°~ 180°)を取得する
                    if (Vector3.Angle(upDirection, betweenDirection) > 90)
                    {
                        // 方向範囲外なら触れていない
                        return false;
                    }
                    break;
                case CheckRange.FrontRange:
                    // 前方方向ベクトルを取得する
                    Vector3 frontDirection = p_TactileRoot.forward;

                    // オブジェクトとの方向ベクトルの角度差(0°~180°)を取得する
                    if (Vector3.Angle(frontDirection, betweenDirection) > 90)
                    {
                        // 方向範囲外なら触れていない
                        return false;
                    }
                    break;
                default:
                    break;
            }

            // 見えていると判定する
            return true;
        }


        /// <summary>
        /// 未登録のゲームオブジェクトをコレクションに登録する
        /// </summary>
        private bool RegistTactileObject(GameObject a_TactileObject)
        {
            bool isResisted = false;

            // 視界オブジェクト情報を作成する
            TactileObjectWrap tactileObjectWrap = new TactileObjectWrap(a_TactileObject);

            // 種別理解情報が付与されているか否か
            if (tactileObjectWrap.ExistObjectFeatures())
            {
                // オブジェクトの存在チェック
                bool isContain = p_TactileBodyCollection.ContainsKey(a_TactileObject);

                if (!isContain)
                {
                    // コレクションに追加する
                    p_TactileBodyCollection.Add(tactileObjectWrap);
                }

                isResisted = true;
            }

            return isResisted;
        }

        /// <summary>
        /// 未検出のゲームオブジェクトをコレクションから除去する
        /// </summary>
        private bool RemoveTactileObject(List<GameObject> a_CheckObjectList)
        {
            bool isRemoved = p_TactileBodyCollection.RemoveWithoutList(a_CheckObjectList);

            return isRemoved;
        }

        /// <summary>
        /// 現在のホロモンの身体スケールを取得する
        /// </summary>
        /// <returns></returns>
        private float CurrentHeightScale()
        {
            // 現在の体の大きさを取得する
            float currentHeight = HoloMonConditionBodySingleton.Instance.
                IReadOnlyReactivePropertyHoloMonBodyStatus.Value.BodyHeight;

            return currentHeight;
        }
    }
}

f:id:bluebirdofoz:20210913061858j:plain

作成したスクリプトをコライダーが設定されているゲームオブジェクトに設定します。
TargetCheckRange の設定を UpRange にしてコライダーの上方に存在するオブジェクトのみを抽出します。
f:id:bluebirdofoz:20210913061910j:plain

動作確認

シーンを再生して動作を確認します。
f:id:bluebirdofoz:20210913061921j:plain

ホロモンの頭部の上方に手をかざすと、ハンドオブジェクトを検出してホロモンが撫でられた反応を示します。
f:id:bluebirdofoz:20210913061931j:plain

一方でホロモンの頭部の下方に手をかざしてもハンドオブジェクトは検出されず、ホロモンは撫でられた反応を示しません。
f:id:bluebirdofoz:20210913061941j:plain