開発環境
Unity : 2019.3.7f1
LuminOS : 0.98.11, APILevel 8
MagicLeap : UnitySDK 0.24.1
MagicLeap : ToolKit 特にバージョン表記等はないので現時点(2020/09/22)での最新
今回開発したアプリのリポジトリはこちらになります
完成するもの
床を判定するやつ pic.twitter.com/zzhb4lk5g2
— 松本隆介 (@matsumotokaka11) September 21, 2020
下準備
ProjectSettings > MagicLeap > ManifestSettingsにて以下の項目にチェックを入れました
- ControllerPose
- LowLatencyLightwear
- WorldReconstruction
スクリプト等
今回のスクリプトはMagicLeap ToolKitのPlaceOnFloorを改造したものです
素のPlaceOnFloorのままだと初回の床判定以降は床判定を行わないので何度でも床判定を行えるようにしました。
改造したFloorChecker.cs
usingSystem.Collections;usingSystem.Collections.Generic;usingUnityEngine;#if PLATFORM_LUMIN
usingUnityEngine.XR.MagicLeap;#endif
namespaceFloorCheck{/// <summary>/// MagicLeapToolsのFloorOnPlaceを改造したクラス./// 床検知を何度もにできるようにする./// </summary>publicclassFloorChecker:MonoBehaviour{readonlyfloatHeadLocationIdleThreshold=0.003f;readonlyfloatHeadRotationIdleThreshold=.3f;readonlyintHistoryCount=5;readonlyfloatHeadIdleRequiredDuration=.2f;// Public Properties:publicVector3Location{get;privateset;}[Tooltip("Does content's content match it's transform forward?")][SerializeField]boolflippedForward;List<Vector3>headLocationHistory;List<Quaternion>headRotationHistory;floatheadLocationVelocity;floatheadRotationVelocity;TransformmainCamera;boolheadLocationIdle;boolheadRotationIdle;boolheadTemporarilyIdle;boolheadIdle;boolplacementValid;//Init:voidAwake(){//refs:mainCamera=Camera.main.transform;//requirements:if(FindObjectOfType<MLSpatialMapper>()==null){Debug.LogError("PlaceOnFloor requires and instance of the MLSpatialMapper in your scene.");}}//Flow:voidOnEnable(){//sets:headLocationHistory=newList<Vector3>();headRotationHistory=newList<Quaternion>();}//Loops:voidUpdate(){//let headpose warmup a little:if(Time.frameCount<3){return;}HeadActivityDetermination();}//Coroutines:IEnumeratorHeadIdleTimeout(){yieldreturnnewWaitForSeconds(HeadIdleRequiredDuration);headIdle=true;}voidHeadActivityDetermination(){//history:headLocationHistory.Add(mainCamera.position);if(HistoryCount<headLocationHistory.Count)headLocationHistory.RemoveAt(0);headRotationHistory.Add(mainCamera.rotation);if(HistoryCount<headRotationHistory.Count)headRotationHistory.RemoveAt(0);//location velocity:if(headLocationHistory.Count==HistoryCount){headLocationVelocity=0;for(inti=1;i<headLocationHistory.Count;i++){headLocationVelocity+=Vector3.Distance(headLocationHistory[i],headLocationHistory[i-1]);}headLocationVelocity/=headLocationHistory.Count;//idle detection:if(headLocationVelocity<=HeadLocationIdleThreshold){if(!headLocationIdle){headLocationIdle=true;}}else{if(headLocationIdle){headLocationIdle=false;}}}//rotation velocity:if(headRotationHistory.Count==HistoryCount){headRotationVelocity=0;for(inti=1;i<headRotationHistory.Count;i++){headRotationVelocity+=Quaternion.Angle(headRotationHistory[i],headRotationHistory[i-1]);}headRotationVelocity/=headRotationHistory.Count;//idle detection:if(headRotationVelocity<=HeadRotationIdleThreshold){if(!headRotationIdle){headRotationIdle=true;}}else{if(headRotationIdle){headRotationIdle=false;}}}//absolute idle head determination:if(headLocationIdle&&headRotationIdle){if(!headTemporarilyIdle){headTemporarilyIdle=true;StartCoroutine(HeadIdleTimeout());}}else{if(headTemporarilyIdle){headIdle=false;headTemporarilyIdle=false;StopCoroutine(HeadIdleTimeout());}}}/// <summary>/// 指定したRayの位置に床があるか否か、ある場合はその座標も返す./// </summary>/// <param name="ray"></param>/// <returns></returns>public(bool,Vector3)LookingAtFloorDetermination(Rayray){//cast to see if we are looking at the floor:RaycastHithit;if(Physics.Raycast(ray,outhit)){MagicLeapTools.SurfaceTypesurface=MagicLeapTools.SurfaceDetails.Analyze(hit);if(surface==MagicLeapTools.SurfaceType.Floor){Location=hit.point;placementValid=true;return(true,Location);}else{placementValid=false;return(false,Vector3.zero);}}else{placementValid=false;return(false,Vector3.zero);}}}}FloorCheckerを利用するFloorCheckOnPlaceContent.cs
usingSystem;usingMagicLeapTools;usingUnityEngine;namespaceFloorCheck{/// <summary>/// トリガを入力したときに床を判定し、床の場合はオブジェクトを配置するサンプル./// </summary>[RequireComponent(typeof(FloorChecker),typeof(AudioSource))]publicclassFloorCheckOnPlaceContent:MonoBehaviour{[SerializeField]AudioClippressClip;[SerializeField]AudioClipsuccessClip;[SerializeField]AudioClipfailedClip;[SerializeField]GameObjectcontent;[SerializeField]Pointerpointer;FloorCheckerfloorChecker;AudioSourceaudio;voidStart(){floorChecker=GetComponent<FloorChecker>();audio=GetComponent<AudioSource>();}publicvoidOnTriggerDown(){audio.PlayOneShot(pressClip);(boolonFloor,Vector3pos)result=floorChecker.LookingAtFloorDetermination(newRay(pointer.Origin,pointer.Direction));if(result.onFloor){audio.PlayOneShot(successClip);content.transform.position=result.pos;}else{audio.PlayOneShot(failedClip);}}}}シーンの構成
シーンの構成は以下の画像の通りになっています
MainCameraは Assets > MagicLeap > Core > Assets > Prefabsにある物を使いました
ControlPointerは Assets > MagicLeap-Tools > Prefabs > Inputから
今回はSpatialMapperを表示してどのメッシュの判定が通っているかをわかりやすくするので Assets > MagicLeap > Core > Assets > PrefabsのMLSpatialMapperも利用します
MLSpatialMapperにはメッシュを生成するルートとなるオブジェクトが必要なのでシーン上にMeshRootオブジェクトを作成しそれをあてがっています
FloorCheckerを利用するクラス等はこのような構成になります
効果音は魔王魂さんから拝借
トリガ入力に対応して床判定を行うためにControlPointerのイベントにFloorCheckOnPlaceContentのOnTriggerDownを登録しています
完成
実機にビルド or ZeroIterationで動作確認をすれば
床を判定するやつ pic.twitter.com/zzhb4lk5g2
— 松本隆介 (@matsumotokaka11) September 21, 2020
これで床を判定し、床だけに配置したいオブジェクトとかの実装ができるようになります
感想
この判定を使えば床判定の入ってるメッシュだけAgentのNavMeshを晴れたりできるかも?
まだやってない、出来たら記事にするかもしれません








