この記事のゴール
MetaQuest3でMRデモ 3. QuestCameraTools-Unity/QRTracking-6000を自作モデルで実装でQRコードで表示させる3Dモデルの位置をコントローラーで微調整できるようにする.
現実空間のものと3Dモデルを重ねる際,QRコードと相対座標で頑張って合わせてもどうしても微妙にずれてしまうだろうと思い,コントローラーで微調整できるスクリプトを作りました.ModelContainerのコンポーネントとして追加してください.
MetaQuest3でMRデモ 4. QuestCameraTools-Unity/QRTracking-6000を時間経過で3Dモデル表示切替とセットにしたバージョンもあるのでどうぞ.
- 現在のTransformの情報が可視化された方が分かりやすいかと思い,Canvas_ui/Status(TextMeshPro)のオブジェクトをModelContainerの子の位置に作っていることを前提にしたコードになっています.
- コントローラーのサムスティックを倒すとオブジェクトが平行移動し,左コントローラーのXボタン(Button.Three)を押すと回転に切り替わります.平行移動に戻したい際はもう一度左コントローラーのXボタンを押してください.
- ゆっくり倒して微調整してください.また,上下左右のうち倒さなかった方の傾きが微妙に適用されるのが嫌だったので,倒れ具合が大きいほうだけ採用するようにしています.
こちらでもdefaultの方にMeshLenderやマテリアルがある際は適宜修正してください.
aligncontroller.cs
using UnityEngine;
using System.Collections.Generic;
using TMPro;
public class AlignController : MonoBehaviour
{
private GameObject targetObject;
[Header("子オブジェクト名(メッシュを差し替える)")]
public string childObjectName = "MeshHolder";
private Transform tmpTransform;
private TextMeshProUGUI tmp_state;
private Transform childTransform;
private bool transformRotationMode = false; // false: 移動, true: 回転
public float moveSpeed = 0.001f;
public float rotateSpeed = 0.1f;
void Start()
{
targetObject = this.gameObject;
childTransform = targetObject.transform.Find(childObjectName);
tmpTransform = transform.Find("Canvas_ui/Status");
tmp_state = tmpTransform.GetComponent<TextMeshProUGUI>();
if (childTransform != null && tmp_state != null)
{
Vector3 pos = childTransform.localPosition;
Vector3 rot = childTransform.localEulerAngles;
tmp_state.text =
$"Mode: {(transformRotationMode ? "Rotation" : "Translation")}\n" +
$"Position: X={pos.x:F2}, Y={pos.y:F2}, Z={pos.z:F2}\n" +
$"Rotation: X={rot.x:F1}, Y={rot.y:F1}, Z={rot.z:F1}\n";
}
}
void Update()
{
// Xボタンでモード切替
if (OVRInput.GetDown(OVRInput.Button.Three))
{
transformRotationMode = !transformRotationMode;
}
// サムスティックによるTransform変更
if (childTransform != null)
{
Vector2 rightStickRaw = OVRInput.Get(OVRInput.Axis2D.SecondaryThumbstick);
Vector2 leftStickRaw = OVRInput.Get(OVRInput.Axis2D.PrimaryThumbstick);
// dominant axis selection(大きい軸のみを残す)
Vector2 rightStick = Vector2.zero;
if (Mathf.Abs(rightStickRaw.x) > Mathf.Abs(rightStickRaw.y))
rightStick.x = rightStickRaw.x;
else
rightStick.y = rightStickRaw.y;
Vector2 leftStick = Vector2.zero;
if (Mathf.Abs(leftStickRaw.x) > Mathf.Abs(leftStickRaw.y))
leftStick.x = leftStickRaw.x;
else
leftStick.y = leftStickRaw.y;
if (!transformRotationMode)
{
// 位置変更
Vector3 deltaPosition = new Vector3(
rightStick.x * moveSpeed,
leftStick.y * moveSpeed,
rightStick.y * moveSpeed
);
childTransform.localPosition += deltaPosition;
}
else
{
// 回転変更
Vector3 deltaRotation = new Vector3(
leftStick.y * rotateSpeed,
rightStick.x * rotateSpeed,
rightStick.y * rotateSpeed
);
childTransform.localEulerAngles += deltaRotation;
}
}
if (childTransform != null && tmp_state != null)
{
Vector3 pos = childTransform.localPosition;
Vector3 rot = childTransform.localEulerAngles;
var connected = OVRInput.GetConnectedControllers();
tmp_state.text =
$"Mode: {(transformRotationMode ? "Rotation" : "Translation")}\n" +
$"Position: X={pos.x:F2}, Y={pos.y:F2}, Z={pos.z:F2}\n" +
$"Rotation: X={rot.x:F1}, Y={rot.y:F1}, Z={rot.z:F1}\n";
}
}
}
changemodel.csとの統合版
using UnityEngine;
using System.Collections.Generic;
using TMPro;
public class ChangeModel_Align : MonoBehaviour
{
[Header("子オブジェクト名(メッシュを差し替える)")]
public string childObjectName = "MeshHolder";
[Header("Resources内のモデル読み込みフォルダ名")]
public string resourcesFolderPath = "Models";
[Header("切り替えフレーム間隔")]
public int frameDelay = 10;
private List<Mesh> meshes = new List<Mesh>();
private List<Material[]> materials = new List<Material[]>();
private int currentMeshIndex = 0;
private int frameCounter = 0;
private GameObject targetObject;
private Mesh originalMesh;
private Material[] originalMaterials;
private bool isCycling = false;
private Transform tmpTransform;
private TextMeshProUGUI tmp_state;
private Transform childTransform;
private bool transformRotationMode = false; // false: 移動, true: 回転
public float moveSpeed = 0.001f;
public float rotateSpeed = 0.1f;
void Start()
{
targetObject = this.gameObject;
Object[] loadedObjects = Resources.LoadAll(resourcesFolderPath, typeof(GameObject));
tmpTransform = transform.Find("Canvas_ui/Status");
tmp_state = tmpTransform.GetComponent<TextMeshProUGUI>();
foreach (Object obj in loadedObjects)
{
GameObject modelPrefab = obj as GameObject;
if (modelPrefab == null) continue;
MeshFilter mf = modelPrefab.GetComponent<MeshFilter>();
MeshRenderer mr = modelPrefab.GetComponent<MeshRenderer>();
if (mf != null && mr != null)
{
meshes.Add(mf.sharedMesh);
materials.Add(mr.sharedMaterials);
}
}
childTransform = targetObject.transform.Find(childObjectName);
if (childTransform != null)
{
MeshFilter mf = childTransform.GetComponent<MeshFilter>();
MeshRenderer mr = childTransform.GetComponent<MeshRenderer>();
if (mf != null && mr != null)
{
originalMesh = mf.sharedMesh;
originalMaterials = mr.sharedMaterials;
}
}
if (childTransform != null && tmp_state != null)
{
Vector3 pos = childTransform.localPosition;
Vector3 rot = childTransform.localEulerAngles;
tmp_state.text =
$"Mode: {(transformRotationMode ? "Rotation" : "Translation")}\n" +
$"Position: X={pos.x:F2}, Y={pos.y:F2}, Z={pos.z:F2}\n" +
$"Rotation: X={rot.x:F1}, Y={rot.y:F1}, Z={rot.z:F1}\n";
}
}
void Update()
{
// Xボタンでモード切替
if (OVRInput.GetDown(OVRInput.Button.Three))
{
transformRotationMode = !transformRotationMode;
}
// Aボタンで切り替え開始
if (OVRInput.GetDown(OVRInput.Button.One))
{
isCycling = true;
}
// Bボタンで停止・初期状態に戻す
if (OVRInput.GetDown(OVRInput.Button.Two))
{
isCycling = false;
if (childTransform == null) return;
MeshFilter mf = childTransform.GetComponent<MeshFilter>();
MeshRenderer mr = childTransform.GetComponent<MeshRenderer>();
if (mf != null && mr != null)
{
mf.mesh = originalMesh;
mr.materials = originalMaterials;
}
currentMeshIndex = 0;
frameCounter = 0;
}
// サムスティックによるTransform変更
if (childTransform != null)
{
Vector2 rightStickRaw = OVRInput.Get(OVRInput.Axis2D.SecondaryThumbstick);
Vector2 leftStickRaw = OVRInput.Get(OVRInput.Axis2D.PrimaryThumbstick);
// dominant axis selection(大きい軸のみを残す)
Vector2 rightStick = Vector2.zero;
if (Mathf.Abs(rightStickRaw.x) > Mathf.Abs(rightStickRaw.y))
rightStick.x = rightStickRaw.x;
else
rightStick.y = rightStickRaw.y;
Vector2 leftStick = Vector2.zero;
if (Mathf.Abs(leftStickRaw.x) > Mathf.Abs(leftStickRaw.y))
leftStick.x = leftStickRaw.x;
else
leftStick.y = leftStickRaw.y;
if (!transformRotationMode)
{
// 位置変更
Vector3 deltaPosition = new Vector3(
rightStick.x * moveSpeed,
leftStick.y * moveSpeed,
rightStick.y * moveSpeed
);
childTransform.localPosition += deltaPosition;
}
else
{
// 回転変更
Vector3 deltaRotation = new Vector3(
leftStick.y * rotateSpeed,
rightStick.x * rotateSpeed,
rightStick.y * rotateSpeed
);
childTransform.localEulerAngles += deltaRotation;
}
}
if (childTransform != null && tmp_state != null)
{
Vector3 pos = childTransform.localPosition;
Vector3 rot = childTransform.localEulerAngles;
var connected = OVRInput.GetConnectedControllers();
tmp_state.text =
$"Mode: {(transformRotationMode ? "Rotation" : "Translation")}\n" +
$"Position: X={pos.x:F2}, Y={pos.y:F2}, Z={pos.z:F2}\n" +
$"Rotation: X={rot.x:F1}, Y={rot.y:F1}, Z={rot.z:F1}\n";
}
frameCounter++;
if (!isCycling || meshes.Count == 0)
return;
if (frameCounter >= frameDelay)
{
frameCounter = 0;
if (childTransform == null) return;
MeshFilter mf = childTransform.GetComponent<MeshFilter>();
MeshRenderer mr = childTransform.GetComponent<MeshRenderer>();
if (mf != null && mr != null)
{
mf.mesh = meshes[currentMeshIndex];
mr.materials = materials[currentMeshIndex];
currentMeshIndex = (currentMeshIndex + 1) % meshes.Count;
}
}
}
}