MagicLeap에서 의미의 평면에 오브제크트를 실행하는 방법
開発環境
유니티 : 2019.3.7f1
LuminOS : 0.98.11, API레벨 8
MagicLeap : UnitySDK 0.24.1
MagicLeap : ToolKit 특별 버전 表記等はないので現時点(2020/09/23)での最新
MagicLeapToolKitのDLはこちらから
今回開発したアプリの リポジトリはこちら
PlaneCheck신에 산프루가 配置하고 있습니다
完成するもの
任意の平面を判定するやつ pic.twitter.com/bJ8k1Udb5k — 松本隆介(
)
下準備
この辺りは前回の記事と同様なので飛ばしても大丈夫です
ProjectSettings > MagicLeap > ManifestSettings
にて以下の項目にcheckkを入れました
신노체성
基本的には前回とはあmari変わってませんがどの平面を判定しているかを確認するためRuntimeConsoleをnewたに追加しました
RuntimeConsoleは
MagicLeap-Tools > Prefabs > Debugging
にあるPrehabをshin上に配置しています
PlaneChecker오브제크트에 첨부할 수 있는 스크립트로 사진을 찍을 수 있습니다.
스크립트
前回作成したFloorCheckerをさらに改造したPlaneCheckerを作成します
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if PLATFORM_LUMIN
using UnityEngine.XR.MagicLeap;
#endif
namespace PlaneCheck
{
/// <summary>
/// MagicLeapToolsのFloorOnPlaceを改造したクラス.
/// 任意の平面を判定する.
/// </summary>
public class PlaneChecker : MonoBehaviour
{
readonly float HeadLocationIdleThreshold = 0.003f;
readonly float HeadRotationIdleThreshold = .3f;
readonly int HistoryCount = 5;
readonly float HeadIdleRequiredDuration = .2f;
public Vector3 Location
{
get;
private set;
}
List<Vector3> headLocationHistory;
List<Quaternion> headRotationHistory;
float headLocationVelocity;
float headRotationVelocity;
Transform mainCamera;
bool headLocationIdle;
bool headRotationIdle;
bool headTemporarilyIdle;
bool headIdle;
bool placementValid;
void Awake()
{
mainCamera = Camera.main.transform;
if (FindObjectOfType<MLSpatialMapper>() == null)
{
Debug.LogError("PlaceOnFloor requires and instance of the MLSpatialMapper in your scene.");
}
}
void OnEnable()
{
headLocationHistory = new List<Vector3>();
headRotationHistory = new List<Quaternion>();
}
void Update()
{
if (Time.frameCount < 3)
{
return;
}
HeadActivityDetermination();
}
IEnumerator HeadIdleTimeout()
{
yield return new WaitForSeconds(HeadIdleRequiredDuration);
headIdle = true;
}
void HeadActivityDetermination()
{
//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 (int i = 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 (int i = 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>
/// <param name="surfaceType"></param>
/// <returns></returns>
public (bool, Vector3) LookingAtFloorDetermination(
Ray ray,
MagicLeapTools.SurfaceType surfaceType)
{
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
MagicLeapTools.SurfaceType surface = MagicLeapTools.SurfaceDetails.Analyze(hit);
if (surface == surfaceType)
{
Location = hit.point;
placementValid = true;
return (true, Location);
}
else
{
placementValid = false;
return (false, Vector3.zero);
}
}
else
{
placementValid = false;
return (false, Vector3.zero);
}
}
}
}
この部分で任意の平面かの判定をとっています
/// <summary>
/// 指定したRayの位置に任意の面があるか否か、ある場合はその座標も返す.
/// </summary>
/// <param name="ray"></param>
/// <param name="surfaceType"></param>
/// <returns></returns>
public (bool, Vector3) LookingAtFloorDetermination(
Ray ray,
MagicLeapTools.SurfaceType surfaceType)
{
RaycastHit hit;
if (Physics.Raycast(ray, out hit))
{
MagicLeapTools.SurfaceType surface = MagicLeapTools.SurfaceDetails.Analyze(hit);
if (surface == surfaceType)
{
Location = hit.point;
placementValid = true;
return (true, Location);
}
else
{
placementValid = false;
return (false, Vector3.zero);
}
}
else
{
placementValid = false;
return (false, Vector3.zero);
}
}
判定する ことが出来る平面はMagicLeapTools의SurfaceDetils.csに定義されています
今回の三種類を判定する ことにしました
PlaneChecker를 사용하려면 PlaneCheckOnPlaceContent.cs
基本的な構成は前回のFloorCheckOnPlaceContent.cs와 あまり変わっていません
범퍼보탄을 押下したら判定を切り替えるようにしています
ControlPointer오브제크트의 ControlInputにOnBumperButtonDown()を登録
using MagicLeapTools;
using UnityEngine;
namespace PlaneCheck
{
/// <summary>
/// トリガを入力したときに任意の平面を判定し、床の場合はオブジェクトを配置するサンプル.
/// </summary>
[RequireComponent(typeof(PlaneChecker),typeof(AudioSource))]
public class PlaneCheckOnPlaceContent : MonoBehaviour
{
[SerializeField] AudioClip pressClip;
[SerializeField] AudioClip successClip;
[SerializeField] AudioClip failedClip;
[SerializeField] GameObject content;
[SerializeField] Pointer pointer;
[SerializeField] RuntimeConsole runtimeConsole;
PlaneChecker planeChecker;
AudioSource audio;
SurfaceType[] surfaceTypes;
int index = 0;
void Start()
{
planeChecker = GetComponent<PlaneChecker>();
audio = GetComponent<AudioSource>();
// RuntimeConsoleに自分が指定した文字列だけ表示したい.
runtimeConsole.errors = false;
runtimeConsole.logs = false;
runtimeConsole.warnings = false;
// 今回はこの三種類のみをチェック.
surfaceTypes = new[]
{
SurfaceType.Floor, // 床.
SurfaceType.Wall, // 壁.
SurfaceType.Ceiling, // 天井.
};
}
public void OnBumperButtonDown()
{
index = (int)Mathf.Repeat(index + 1, surfaceTypes.Length);
runtimeConsole.logText.text = surfaceTypes[index].ToString();
}
public void OnTriggerDown()
{
audio.PlayOneShot(pressClip);
(bool onSurfaceType, Vector3 pos ) result = planeChecker.LookingAtFloorDetermination(new Ray(pointer.Origin, pointer.Direction), surfaceTypes[index]);
if (result.onSurfaceType)
{
audio.PlayOneShot(successClip);
content.transform.position = result.pos;
}
else
{
audio.PlayOneShot(failedClip);
}
}
}
}
完成
これでデプロイ or ZeorIterationで確認すれば↓の様な動作になります
ただしRuntimeConsoleは実機にデプロイ時にしか動作しないので実機での確認のほうがどの平面を判定しているかがわかりやすいです
任意の平面を判定するやつ pic.twitter.com/bJ8k1Udb5k — 松本隆介(
)
感想
これで任意の平面にOBJECQUETOを配置したりできるようになりました
なんか家具の配置確認アプリ的なもので天井にランプをつるすとか、壁に絵を飾るとかの確認に使うのがmein所の使い方かな?
Reference
이 문제에 관하여(MagicLeap에서 의미의 평면에 오브제크트를 실행하는 방법), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/matsumotokaka11/magicleap-3710텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)