This repository has been archived on 2025-04-28. You can view files and clone it, but cannot push or open issues or pull requests.
ARPlusSystem/ARPlusSystem-250418/Library/PackageCache/com.unity.xr.arfoundation@4.../Runtime/AR/ARSessionOrigin.cs

310 lines
14 KiB
C#
Raw Normal View History

using System;
#if USE_LEGACY_INPUT_HELPERS
using UnityEngine.SpatialTracking;
#endif
namespace UnityEngine.XR.ARFoundation
{
/// <summary>
/// An <c>ARSessionOrigin</c> is the parent for an AR setup. It contains a <c>Camera</c> and
/// any <c>GameObject</c>s created from detected features, such as planes or point clouds.
/// </summary>
/// <remarks>
/// Session space vs. Unity space
///
/// Because an AR device will be used to drive the <c>Camera</c>'s position and rotation,
/// you cannot directly place the <c>Camera</c> at an arbitrary position in the Unity scene.
/// Instead, you should position the <c>ARSessionOrigin</c>. This makes the <c>Camera</c>
/// (and any detected features) relative to that as a result.
///
/// It is important to keep the <c>Camera</c> and detected features in the same space relative to
/// each other (otherwise, detected features like planes won't appear in the correct place relative
/// to the <c>Camera</c>). The space relative to the AR device's starting position is called
/// "session space" or "device space". For example, when the AR session begins, the device might
/// report its position as (0, 0, 0). Detected features, such as planes, will be reported relative
/// to this starting position. The purpose of the <c>ARSessionOrigin</c> is to convert the session space
/// to Unity world space.
///
/// To facilitate this, the <c>ARSessionOrigin</c> creates a new <c>GameObject</c> called "Trackables"
/// as a sibling of its <c>Camera</c>. This should be the parent <c>GameObject</c> for all
/// detected features.
///
/// At runtime, a typical scene graph might look like this:
/// - AR Session Origin
/// - Camera
/// - Trackables
/// - Detected plane 1
/// - Detected plane 2
/// - Point cloud
/// - etc...
///
/// You can access the "trackables" <c>GameObject</c> with <see cref="trackablesParent"/>.
///
/// Note that the <c>localPosition</c> and <c>localRotation</c> of detected trackables
/// remain in real-world meters relative to the AR device's starting position and rotation.
///
/// Scale
///
/// If you want to scale the content rendered by the <c>ARSessionOrigin</c> you should apply
/// the scale to the <c>ARSessionOrigin</c>'s transform. This is preferrable to scaling
/// the content directly, which can have undesirable side effects. Physics and NavMeshes,
/// for example, do not perform well when scaled very small.
/// </remarks>
[DisallowMultipleComponent]
[HelpURL(HelpUrls.ApiWithNamespace + nameof(ARSessionOrigin) + ".html")]
public class ARSessionOrigin : MonoBehaviour
{
[SerializeField]
[Tooltip("The Camera to associate with the AR device.")]
Camera m_Camera;
/// <summary>
/// The <c>Camera</c> to associate with the AR device. It must be a child of this <c>ARSessionOrigin</c>.
/// </summary>
/// <remarks>
/// The <c>Camera</c> should update its position and rotation according to the AR device.
/// This is typically accomplished by adding a <c>TrackedPoseDriver</c> component to the
/// <c>Camera</c>.
/// </remarks>
#if UNITY_EDITOR
public new Camera camera
#else
public Camera camera
#endif
{
get { return m_Camera; }
set { m_Camera = value; }
}
/// <summary>
/// The parent <c>Transform</c> for all "trackables" (for example, planes and feature points).
/// </summary>
public Transform trackablesParent { get; private set; }
/// <summary>
/// Invoked during
/// [Application.onBeforeRender](xref:UnityEngine.Application.onBeforeRender(UnityEngine.Events.UnityAction))
/// whenever the <see cref="trackablesParent"/> [transform](xref:UnityEngine.Transform) changes.
/// </summary>
public event Action<ARTrackablesParentTransformChangedEventArgs> trackablesParentTransformChanged;
GameObject m_ContentOffsetGameObject;
Transform contentOffsetTransform
{
get
{
if (m_ContentOffsetGameObject == null)
{
// Insert a GameObject directly below the rig
m_ContentOffsetGameObject = new GameObject("Content Placement Offset");
m_ContentOffsetGameObject.transform.SetParent(transform, false);
// Re-parent any children of the ARSessionOrigin
for (var i = 0; i < transform.childCount; ++i)
{
var child = transform.GetChild(i);
if (child != m_ContentOffsetGameObject.transform)
{
child.SetParent(m_ContentOffsetGameObject.transform, true);
--i; // Decrement because childCount is also one less.
}
}
}
return m_ContentOffsetGameObject.transform;
}
}
/// <summary>
/// Makes <paramref name="content"/> appear to be placed at <paramref name="position"/> with orientation <paramref name="rotation"/>.
/// </summary>
/// <param name="content">The <c>Transform</c> of the content you wish to affect.</param>
/// <param name="position">The position you wish the content to appear at. This could be
/// a position on a detected plane, for example.</param>
/// <param name="rotation">The rotation the content should appear to be in, relative
/// to the <c>Camera</c>.</param>
/// <remarks>
/// This method does not actually change the <c>Transform</c> of content; instead,
/// it updates the <c>ARSessionOrigin</c>'s <c>Transform</c> so the content
/// appears to be at the given position and rotation. This is useful for placing AR
/// content onto surfaces when the content itself cannot be moved at runtime.
/// For example, if your content includes terrain or a NavMesh, it cannot
/// be moved or rotated dynamically.
/// </remarks>
public void MakeContentAppearAt(Transform content, Vector3 position, Quaternion rotation)
{
MakeContentAppearAt(content, position);
MakeContentAppearAt(content, rotation);
}
/// <summary>
/// Makes <paramref name="content"/> appear to be placed at <paramref name="position"/>.
/// </summary>
/// <param name="content">The <c>Transform</c> of the content you wish to affect.</param>
/// <param name="position">The position you wish the content to appear at. This could be
/// a position on a detected plane, for example.</param>
/// <remarks>
/// This method does not actually change the <c>Transform</c> of content; instead,
/// it updates the <c>ARSessionOrigin</c>'s <c>Transform</c> so the content
/// appears to be at the given position.
/// </remarks>
public void MakeContentAppearAt(Transform content, Vector3 position)
{
if (content == null)
throw new ArgumentNullException("content");
// Adjust the "point of interest" transform to account
// for the actual position we want the content to appear at.
contentOffsetTransform.position += transform.position - position;
// The ARSessionOrigin's position needs to match the content's pivot. This is so
// the entire ARSessionOrigin rotates around the content (so the impression is that
// the content is rotating, not the rig).
transform.position = content.position;
}
/// <summary>
/// Makes <paramref name="content"/> appear to have orientation <paramref name="rotation"/> relative to the <c>Camera</c>.
/// </summary>
/// <param name="content">The <c>Transform</c> of the content you wish to affect.</param>
/// <param name="rotation">The rotation the content should appear to be in, relative
/// to the <c>Camera</c>.</param>
/// <remarks>
/// This method does not actually change the <c>Transform</c> of content; instead,
/// it updates the <c>ARSessionOrigin</c>'s <c>Transform</c> so that the content
/// appears to be in the requested orientation.
/// </remarks>
public void MakeContentAppearAt(Transform content, Quaternion rotation)
{
if (content == null)
throw new ArgumentNullException("content");
// Since we aren't rotating the content, we need to perform the inverse
// operation on the ARSessionOrigin. For example, if we want the
// content to appear to be rotated 90 degrees on the Y axis, we should
// rotate our rig -90 degrees on the Y axis.
transform.rotation = Quaternion.Inverse(rotation) * content.rotation;
}
void Awake()
{
// This will be the parent GameObject for any trackables (such as planes) for which
// we want a corresponding GameObject.
trackablesParent = (new GameObject("Trackables")).transform;
trackablesParent.SetParent(transform, false);
trackablesParent.localPosition = Vector3.zero;
trackablesParent.localRotation = Quaternion.identity;
trackablesParent.localScale = Vector3.one;
if (camera)
{
var arPoseDriver = camera.GetComponent<ARPoseDriver>();
#if USE_LEGACY_INPUT_HELPERS
var trackedPoseDriver = camera.GetComponent<TrackedPoseDriver>();
// Warn if not using a ARPoseDriver or a TrackedPoseDriver
if (arPoseDriver == null && trackedPoseDriver == null)
{
Debug.LogWarning(
$"Camera \"{camera.name}\" does not use a AR Pose Driver or a Tracked Pose Driver, " +
"so its transform will not be updated by an XR device. In order for this to be " +
"updated, please add either an AR Pose Driver or a Tracked Pose Driver.");
}
// If we are using an TrackedPoseDriver, and the user hasn't chosen "make relative"
// then warn if the camera has a non-identity transform (since it will be overwritten).
else if ((trackedPoseDriver != null && !trackedPoseDriver.UseRelativeTransform))
{
var cameraTransform = camera.transform;
if ((cameraTransform.localPosition != Vector3.zero) || (cameraTransform.localRotation != Quaternion.identity))
{
Debug.LogWarning(
$"Camera \"{camera.name}\" has a non-identity transform " +
$"(position = {cameraTransform.localPosition}, rotation = {cameraTransform.localRotation}). " +
"The camera's local position and rotation will be overwritten by the XR device, " +
"so this starting transform will have no effect. Tick the \"Make Relative\" " +
"checkbox on the camera's Tracked Pose Driver to apply this starting transform.");
}
}
// If using ARPoseDriver then it will get overwritten no matter what
else
{
var cameraTransform = camera.transform;
if ((cameraTransform.localPosition != Vector3.zero) || (cameraTransform.localRotation != Quaternion.identity))
{
Debug.LogWarning(
$"Camera \"{camera.name}\" has a non-identity transform " +
$"(position = {cameraTransform.localPosition}, rotation = {cameraTransform.localRotation}). " +
"The camera's local position and rotation will be overwritten by the XR device.");
}
}
#else // !(USE_LEGACY_INPUT_HELPERS
if (arPoseDriver == null)
{
Debug.LogWarning(
$"Camera \"{camera.name}\" does not use a AR Pose Driver, so its transform will not be updated by " +
"an XR device. In order for this to be updated, please add an AR Pose Driver component.");
}
#endif // USE_LEGACY_INPUT_HELPERS
}
}
Pose GetCameraOriginPose()
{
var localOriginPose = Pose.identity;
var parent = camera.transform.parent;
#if USE_LEGACY_INPUT_HELPERS
var trackedPoseDriver = camera.GetComponent<TrackedPoseDriver>();
if (trackedPoseDriver)
{
localOriginPose = trackedPoseDriver.originPose;
}
#endif // USE_LEGACY_INPUT_HELPERS
return parent
? parent.TransformPose(localOriginPose)
: localOriginPose;
}
void OnEnable() => Application.onBeforeRender += OnBeforeRender;
void OnDisable() => Application.onBeforeRender -= OnBeforeRender;
void OnBeforeRender()
{
if (camera)
{
var pose = GetCameraOriginPose();
trackablesParent.position = pose.position;
trackablesParent.rotation = pose.rotation;
}
if (trackablesParent.hasChanged)
{
trackablesParentTransformChanged?.Invoke(
new ARTrackablesParentTransformChangedEventArgs(this, trackablesParent));
trackablesParent.hasChanged = false;
}
}
#if UNITY_EDITOR && USE_LEGACY_INPUT_HELPERS
void OnValidate()
{
if (camera)
{
if ((camera.GetComponent<TrackedPoseDriver>()?.enabled ?? false) &&
(camera.GetComponent<ARPoseDriver>()?.enabled ?? false))
{
Debug.LogWarning(
$"Camera \"{camera.name}\" has an AR Pose Driver and a Tracked Pose Driver and both are enabled. " +
"This configuration will cause the camera transform to be updated from both components in a non-deterministic " +
"way. This is likely an unintended error.");
}
}
}
#endif // UNITY_EDITOR && USE_LEGACY_INPUT_HELPERS
}
}