581 lines
22 KiB
C#
581 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Runtime.CompilerServices;
|
|
using UnityEngine.Experimental.XR.Interaction;
|
|
|
|
#if ENABLE_AR || ENABLE_VR
|
|
using UnityEngine.XR;
|
|
#endif
|
|
|
|
[assembly: InternalsVisibleTo("UnityEditor.SpatialTracking")]
|
|
|
|
namespace UnityEngine.SpatialTracking
|
|
{
|
|
internal class TrackedPoseDriverDataDescription
|
|
{
|
|
internal struct PoseData
|
|
{
|
|
public List<string> PoseNames;
|
|
public List<TrackedPoseDriver.TrackedPose> Poses;
|
|
}
|
|
|
|
internal static List<PoseData> DeviceData = new List<PoseData>
|
|
{
|
|
// Generic XR Device
|
|
new PoseData
|
|
{
|
|
PoseNames = new List<string>
|
|
{
|
|
"Left Eye", "Right Eye", "Center Eye - HMD Reference", "Head", "Color Camera"
|
|
},
|
|
Poses = new List<TrackedPoseDriver.TrackedPose>
|
|
{
|
|
TrackedPoseDriver.TrackedPose.LeftEye,
|
|
TrackedPoseDriver.TrackedPose.RightEye,
|
|
TrackedPoseDriver.TrackedPose.Center,
|
|
TrackedPoseDriver.TrackedPose.Head,
|
|
TrackedPoseDriver.TrackedPose.ColorCamera
|
|
}
|
|
},
|
|
// generic controller
|
|
new PoseData
|
|
{
|
|
PoseNames = new List<string>
|
|
{
|
|
"Left Controller", "Right Controller"
|
|
},
|
|
Poses = new List<TrackedPoseDriver.TrackedPose>
|
|
{
|
|
TrackedPoseDriver.TrackedPose.LeftPose,
|
|
TrackedPoseDriver.TrackedPose.RightPose
|
|
}
|
|
},
|
|
// generic remote
|
|
new PoseData
|
|
{
|
|
PoseNames = new List<string>
|
|
{
|
|
"Device Pose"
|
|
},
|
|
Poses = new List<TrackedPoseDriver.TrackedPose>
|
|
{
|
|
TrackedPoseDriver.TrackedPose.RemotePose,
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Bitflag enum which represents what data was set on an associated Pose struct
|
|
/// </summary>
|
|
|
|
[Flags]
|
|
public enum PoseDataFlags
|
|
{
|
|
/// <summary>
|
|
/// No data was actually set on the pose
|
|
/// </summary>
|
|
NoData = 0,
|
|
/// <summary>
|
|
/// If this flag is set, position data was updated on the associated pose struct
|
|
/// </summary>
|
|
Position = 1 << 0,
|
|
/// <summary>
|
|
/// If this flag is set, rotation data was updated on the associated pose struct
|
|
/// </summary>
|
|
Rotation = 1 << 1,
|
|
}
|
|
|
|
/// <summary>
|
|
/// The PoseDataSource class acts as a container for the GetDataFromSource method call that should be used by PoseProviders wanting to query data for a particular pose.
|
|
/// </summary>
|
|
public static class PoseDataSource
|
|
{
|
|
#if ENABLE_AR || ENABLE_VR
|
|
static internal List<XR.XRNodeState> nodeStates = new List<XR.XRNodeState>();
|
|
static internal PoseDataFlags GetNodePoseData(XR.XRNode node, out Pose resultPose)
|
|
{
|
|
PoseDataFlags retData = PoseDataFlags.NoData;
|
|
XR.InputTracking.GetNodeStates(nodeStates);
|
|
foreach (XR.XRNodeState nodeState in nodeStates)
|
|
{
|
|
if (nodeState.nodeType == node)
|
|
{
|
|
if (nodeState.TryGetPosition(out resultPose.position))
|
|
{
|
|
retData |= PoseDataFlags.Position;
|
|
}
|
|
if (nodeState.TryGetRotation(out resultPose.rotation))
|
|
{
|
|
retData |= PoseDataFlags.Rotation;
|
|
}
|
|
return retData;
|
|
}
|
|
}
|
|
resultPose = Pose.identity;
|
|
return retData;
|
|
}
|
|
#endif
|
|
|
|
/// <summary>The GetDataFromSource method is used to query data from the XRNode subsystem based on the provided pose source.</summary>
|
|
/// <param name = "poseSource" > The pose source to request data for.</param>
|
|
/// <param name = "resultPose" > The resulting pose data.</param>
|
|
/// <returns>True, if the pose source is valid, otherwise false.</returns>
|
|
public static bool TryGetDataFromSource(TrackedPoseDriver.TrackedPose poseSource, out Pose resultPose)
|
|
{
|
|
return GetDataFromSource(poseSource, out resultPose) == (PoseDataFlags.Position | PoseDataFlags.Rotation);
|
|
}
|
|
|
|
|
|
/// <summary>The GetDataFromSource method is used to query data from the XRNode subsystem based on the provided pose source.</summary>
|
|
/// <param name = "poseSource" > The pose source to request data for.</param>
|
|
/// <param name = "resultPose" > The resulting pose data. This function will return the Center Eye pose if the Color Camera pose is not available. </param>
|
|
/// <returns>Returns a bitflag which represents which data has been retrieved from the provided pose source</returns>
|
|
public static PoseDataFlags GetDataFromSource(TrackedPoseDriver.TrackedPose poseSource, out Pose resultPose)
|
|
{
|
|
#if ENABLE_AR || ENABLE_VR
|
|
switch (poseSource)
|
|
{
|
|
case TrackedPoseDriver.TrackedPose.RemotePose:
|
|
{
|
|
PoseDataFlags retFlags = GetNodePoseData(XR.XRNode.RightHand, out resultPose);
|
|
if (retFlags == PoseDataFlags.NoData)
|
|
return GetNodePoseData(XR.XRNode.LeftHand, out resultPose);
|
|
return retFlags;
|
|
}
|
|
case TrackedPoseDriver.TrackedPose.LeftEye:
|
|
{
|
|
return GetNodePoseData(XR.XRNode.LeftEye, out resultPose);
|
|
}
|
|
case TrackedPoseDriver.TrackedPose.RightEye:
|
|
{
|
|
return GetNodePoseData(XR.XRNode.RightEye, out resultPose);
|
|
}
|
|
case TrackedPoseDriver.TrackedPose.Head:
|
|
{
|
|
return GetNodePoseData(XR.XRNode.Head, out resultPose);
|
|
}
|
|
case TrackedPoseDriver.TrackedPose.Center:
|
|
{
|
|
return GetNodePoseData(XR.XRNode.CenterEye, out resultPose);
|
|
}
|
|
case TrackedPoseDriver.TrackedPose.LeftPose:
|
|
{
|
|
return GetNodePoseData(XR.XRNode.LeftHand, out resultPose);
|
|
}
|
|
case TrackedPoseDriver.TrackedPose.RightPose:
|
|
{
|
|
return GetNodePoseData(XR.XRNode.RightHand, out resultPose);
|
|
}
|
|
case TrackedPoseDriver.TrackedPose.ColorCamera:
|
|
{
|
|
// We fall back to CenterEye because we can't currently extend the XRNode structure, nor are we ready to replace it.
|
|
return GetNodePoseData(XR.XRNode.CenterEye, out resultPose);
|
|
}
|
|
default:
|
|
{
|
|
Debug.LogWarningFormat("Unable to retrieve pose data for poseSource: {0}", poseSource.ToString());
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
resultPose = Pose.identity;
|
|
return PoseDataFlags.NoData;
|
|
}
|
|
}
|
|
|
|
// The DefaultExecutionOrder is needed because TrackedPoseDriver does some
|
|
// of its work in regular Update and FixedUpdate calls, but this needs to
|
|
// be done before regular user scripts have their own Update and
|
|
// FixedUpdate calls, in order that they correctly get the values for this
|
|
// frame and not the previous.
|
|
// -32000 is the minimal possible execution order value; -30000 makes it
|
|
// unlikely users chose lower values for their scripts by accident, but
|
|
// still allows for the possibility.
|
|
|
|
/// <summary>
|
|
/// The TrackedPoseDriver component applies the current Pose value of a tracked device to the transform of the GameObject.
|
|
/// TrackedPoseDriver can track multiple types of devices including XR HMDs, controllers, and remotes.
|
|
/// </summary>
|
|
[DefaultExecutionOrder(-30000)]
|
|
[Serializable]
|
|
[AddComponentMenu("XR/Tracked Pose Driver")]
|
|
[HelpURL("https://docs.unity3d.com/Packages/com.unity.xr.legacyinputhelpers@2.1/manual/index.html")]
|
|
public class TrackedPoseDriver : MonoBehaviour
|
|
{
|
|
/// <summary>
|
|
/// The device being tracked by the tracked pose driver
|
|
/// </summary>
|
|
public enum DeviceType
|
|
{
|
|
/// <summary>
|
|
/// An XR Controller, use this value for controllers
|
|
/// </summary>
|
|
GenericXRDevice = 0,
|
|
/// <summary>
|
|
/// An Generic XR Devices, use this value for HMD and AR Mobile device tracking
|
|
/// </summary>
|
|
GenericXRController = 1,
|
|
/// <summary>
|
|
/// An XR Remote, use this value for mobile remotes
|
|
/// </summary>
|
|
GenericXRRemote = 2
|
|
}
|
|
|
|
/// <summary>
|
|
/// The list of endpoints that users can track with the <see cref="TrackedPoseDriver"/>
|
|
/// </summary>
|
|
public enum TrackedPose
|
|
{
|
|
/// <summary>
|
|
/// The left eye of a HMD style device
|
|
/// </summary>
|
|
LeftEye = 0,
|
|
/// <summary>
|
|
/// The right eye of a HMD style device
|
|
/// </summary>
|
|
RightEye = 1,
|
|
/// <summary>
|
|
/// The center eye of a HMD style device, this is usually the default for most HMDs
|
|
/// </summary>
|
|
Center = 2,
|
|
/// <summary>
|
|
/// The head eye of a HMD style device
|
|
/// </summary>
|
|
Head = 3,
|
|
/// <summary>
|
|
/// The left hand controller pose
|
|
/// </summary>
|
|
LeftPose = 4,
|
|
/// <summary>
|
|
/// The right hand controller pose
|
|
/// </summary>
|
|
RightPose = 5,
|
|
/// <summary>
|
|
/// The color camera of a mobile device
|
|
/// </summary>
|
|
ColorCamera = 6,
|
|
/// <summary>
|
|
/// No Longer Used
|
|
/// </summary>
|
|
DepthCameraDeprecated = 7,
|
|
/// <summary>
|
|
/// No Longer Used
|
|
/// </summary>
|
|
FisheyeCameraDeprected = 8,
|
|
/// <summary>
|
|
/// No Longer Used
|
|
/// </summary>
|
|
DeviceDeprecated = 9,
|
|
/// <summary>
|
|
/// The pose of a mobile remote
|
|
/// </summary>
|
|
RemotePose = 10,
|
|
}
|
|
|
|
[SerializeField]
|
|
DeviceType m_Device;
|
|
/// <summary>
|
|
/// This is used to indicate which pose the TrackedPoseDriver is currently tracking.
|
|
/// </summary>
|
|
public DeviceType deviceType
|
|
{
|
|
get { return m_Device; }
|
|
internal set { m_Device = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
TrackedPose m_PoseSource = TrackedPoseDriver.TrackedPose.Center;
|
|
/// <summary>
|
|
/// The pose being tracked by the tracked pose driver
|
|
/// </summary>
|
|
public TrackedPose poseSource
|
|
{
|
|
get { return m_PoseSource; }
|
|
internal set { m_PoseSource = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method is used to set the device / pose pair for the SpatialTracking.TrackedPoseDriver. setting an invalid combination of these values will return false.
|
|
/// </summary>
|
|
/// <param name="deviceType">The device type that we wish to track </param>
|
|
/// <param name="pose">The pose source that we wish to track</param>
|
|
/// <returns>true if the values provided are sensible, otherwise false</returns>
|
|
public bool SetPoseSource(DeviceType deviceType, TrackedPose pose)
|
|
{
|
|
if ((int)deviceType < TrackedPoseDriverDataDescription.DeviceData.Count)
|
|
{
|
|
TrackedPoseDriverDataDescription.PoseData val = TrackedPoseDriverDataDescription.DeviceData[(int)deviceType];
|
|
for (int i = 0; i < val.Poses.Count; ++i)
|
|
{
|
|
if (val.Poses[i] == pose)
|
|
{
|
|
this.deviceType = deviceType;
|
|
poseSource = pose;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
[SerializeField]
|
|
BasePoseProvider m_PoseProviderComponent;
|
|
/// <summary>
|
|
/// Optional: This field holds the reference to the BasePoseProvider instance that, if set, will be used to override the behavior of
|
|
/// the TrackedPoseDriver. When this field is empty, the TrackedPoseDriver will operate as per usual, with pose data being
|
|
/// retrieved from the device or pose settings of the TrackedPoseDriver. When this field is set, the pose data will be
|
|
/// provided by the attached BasePoseProvider. The device or pose fields will be hidden as they are no longer used to
|
|
/// control the parent GameObject Transform.
|
|
/// </summary>
|
|
public BasePoseProvider poseProviderComponent
|
|
{
|
|
get { return m_PoseProviderComponent; }
|
|
set { m_PoseProviderComponent = value; }
|
|
}
|
|
|
|
PoseDataFlags GetPoseData(DeviceType device, TrackedPose poseSource, out Pose resultPose)
|
|
{
|
|
return m_PoseProviderComponent != null
|
|
? m_PoseProviderComponent.GetPoseFromProvider(out resultPose)
|
|
: PoseDataSource.GetDataFromSource(poseSource, out resultPose);
|
|
}
|
|
|
|
/// <summary>
|
|
/// This enum is used to indicate which parts of the pose will be applied to the parent transform
|
|
/// </summary>
|
|
public enum TrackingType
|
|
{
|
|
/// <summary>
|
|
/// With this setting, both the pose's rotation and position will be applied to the parent transform
|
|
/// </summary>
|
|
RotationAndPosition,
|
|
/// <summary>
|
|
/// With this setting, only the pose's rotation will be applied to the parent transform
|
|
/// </summary>
|
|
RotationOnly,
|
|
/// <summary>
|
|
/// With this setting, only the pose's position will be applied to the parent transform
|
|
/// </summary>
|
|
PositionOnly
|
|
}
|
|
|
|
[SerializeField]
|
|
TrackingType m_TrackingType;
|
|
/// <summary>
|
|
/// The tracking type being used by the tracked pose driver
|
|
/// </summary>
|
|
public TrackingType trackingType
|
|
{
|
|
get { return m_TrackingType; }
|
|
set { m_TrackingType = value; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// The update type being used by the tracked pose driver
|
|
/// </summary>
|
|
public enum UpdateType
|
|
{
|
|
/// <summary>
|
|
/// Sample input at both update, and directly before rendering. For smooth head pose tracking,
|
|
/// we recommend using this value as it will provide the lowest input latency for the device.
|
|
/// This is the default value for the UpdateType option
|
|
/// </summary>
|
|
UpdateAndBeforeRender,
|
|
/// <summary>
|
|
/// Only sample input during the update phase of the frame.
|
|
/// </summary>
|
|
Update,
|
|
/// <summary>
|
|
/// Only sample input directly before rendering
|
|
/// </summary>
|
|
BeforeRender,
|
|
}
|
|
|
|
[SerializeField]
|
|
UpdateType m_UpdateType = UpdateType.UpdateAndBeforeRender;
|
|
/// <summary>
|
|
/// The update type being used by the tracked pose driver
|
|
/// </summary>
|
|
public UpdateType updateType
|
|
{
|
|
get { return m_UpdateType; }
|
|
set { m_UpdateType = value; }
|
|
}
|
|
|
|
[SerializeField]
|
|
bool m_UseRelativeTransform = false;
|
|
/// <summary>
|
|
/// This is used to indicate whether the TrackedPoseDriver will use the object's original transform as its basis.
|
|
/// </summary>
|
|
public bool UseRelativeTransform
|
|
{
|
|
get { return m_UseRelativeTransform; }
|
|
set { m_UseRelativeTransform = value; }
|
|
}
|
|
/// <summary>
|
|
/// The origin pose is the offset applied to any tracking data. This is only used when in legacy compatibility mode.
|
|
/// </summary>
|
|
protected Pose m_OriginPose;
|
|
/// <summary>
|
|
/// originPose is an offset applied to any tracking data read from this object.
|
|
/// Setting this value should be reserved for dealing with edge-cases, such as
|
|
/// achieving parity between room-scale (floor centered) and stationary (head centered)
|
|
/// tracking - without having to alter the transform hierarchy.
|
|
/// For user locomotion and gameplay purposes you are usually better off just
|
|
/// moving the parent transform of this object.
|
|
/// </summary>
|
|
public Pose originPose
|
|
{
|
|
get { return m_OriginPose; }
|
|
set { m_OriginPose = value; }
|
|
}
|
|
|
|
private void CacheLocalPosition()
|
|
{
|
|
m_OriginPose.position = transform.localPosition;
|
|
m_OriginPose.rotation = transform.localRotation;
|
|
}
|
|
|
|
private void ResetToCachedLocalPosition()
|
|
{
|
|
SetLocalTransform(m_OriginPose.position, m_OriginPose.rotation, PoseDataFlags.Position | PoseDataFlags.Rotation);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected virtual void Awake()
|
|
{
|
|
CacheLocalPosition();
|
|
#if UNITY_2019_3_OR_NEWER
|
|
// deprecated functionality in 2020.1
|
|
#elif ENABLE_AR || ENABLE_VR
|
|
if (HasStereoCamera())
|
|
{
|
|
XRDevice.DisableAutoXRCameraTracking(GetComponent<Camera>(), true);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected virtual void OnDestroy()
|
|
{
|
|
#if UNITY_2019_3_OR_NEWER
|
|
// deprecated functionality in 2020.1
|
|
#elif ENABLE_AR || ENABLE_VR
|
|
if (HasStereoCamera())
|
|
{
|
|
XRDevice.DisableAutoXRCameraTracking(GetComponent<Camera>(), false);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected virtual void OnEnable()
|
|
{
|
|
Application.onBeforeRender += OnBeforeRender;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected virtual void OnDisable()
|
|
{
|
|
// remove delegate registration
|
|
ResetToCachedLocalPosition();
|
|
Application.onBeforeRender -= OnBeforeRender;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected virtual void FixedUpdate()
|
|
{
|
|
if (m_UpdateType == UpdateType.Update ||
|
|
m_UpdateType == UpdateType.UpdateAndBeforeRender)
|
|
{
|
|
PerformUpdate();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
protected virtual void Update()
|
|
{
|
|
if (m_UpdateType == UpdateType.Update ||
|
|
m_UpdateType == UpdateType.UpdateAndBeforeRender)
|
|
{
|
|
PerformUpdate();
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
// For the same reason as DefaultExecutionOrder, a callback order is specified to
|
|
// apply the pose to the Transform before default user scripts execute.
|
|
[BeforeRenderOrder(-30000)]
|
|
protected virtual void OnBeforeRender()
|
|
{
|
|
if (m_UpdateType == UpdateType.BeforeRender ||
|
|
m_UpdateType == UpdateType.UpdateAndBeforeRender)
|
|
{
|
|
PerformUpdate();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the transform that is being driven by the <see cref="TrackedPoseDriver"/>. will only correct set the rotation or position depending on the <see cref="PoseDataFlags"/>
|
|
/// </summary>
|
|
/// <param name="newPosition">The position to apply.</param>
|
|
/// <param name="newRotation">The rotation to apply.</param>
|
|
/// <param name="poseFlags">The flags indiciating which of the position/rotation values are provided by the calling code.</param>
|
|
protected virtual void SetLocalTransform(Vector3 newPosition, Quaternion newRotation, PoseDataFlags poseFlags)
|
|
{
|
|
if ((m_TrackingType == TrackingType.RotationAndPosition ||
|
|
m_TrackingType == TrackingType.RotationOnly) &&
|
|
(poseFlags & PoseDataFlags.Rotation) > 0)
|
|
{
|
|
transform.localRotation = newRotation;
|
|
}
|
|
|
|
if ((m_TrackingType == TrackingType.RotationAndPosition ||
|
|
m_TrackingType == TrackingType.PositionOnly) &&
|
|
(poseFlags & PoseDataFlags.Position) > 0)
|
|
{
|
|
transform.localPosition = newPosition;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// This is only used when running in legacy mode, and will fake the behavior of the old implicit camera tracking. This will transform by the origin pose if necessary.
|
|
/// </summary>
|
|
/// <param name="pose">Pose to transform by the origin if in relative transform mode.</param>
|
|
/// <returns>The pose, with the applied transform if in Relative Transform mode.</returns>
|
|
protected Pose TransformPoseByOriginIfNeeded(Pose pose)
|
|
{
|
|
if (m_UseRelativeTransform)
|
|
{
|
|
return pose.GetTransformedBy(m_OriginPose);
|
|
}
|
|
else
|
|
{
|
|
return pose;
|
|
}
|
|
}
|
|
|
|
private bool HasStereoCamera()
|
|
{
|
|
Camera camera = GetComponent<Camera>();
|
|
return camera != null && camera.stereoEnabled;
|
|
}
|
|
|
|
/// <summary>
|
|
/// PerformUpdate queries the data from the selected pose source, and then calls <see cref="SetLocalTransform"/> to apply the pose.
|
|
/// </summary>
|
|
protected virtual void PerformUpdate()
|
|
{
|
|
if (!enabled)
|
|
return;
|
|
Pose currentPose;
|
|
PoseDataFlags poseFlags = GetPoseData(m_Device, m_PoseSource, out currentPose);
|
|
if (poseFlags != PoseDataFlags.NoData)
|
|
{
|
|
Pose localPose = TransformPoseByOriginIfNeeded(currentPose);
|
|
SetLocalTransform(localPose.position, localPose.rotation, poseFlags);
|
|
}
|
|
}
|
|
}
|
|
}
|