// Copyright 2017 Google Inc. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This provider is only available on an Android device. #if UNITY_ANDROID && !UNITY_EDITOR using Gvr; using System; using System.Runtime.InteropServices; using UnityEngine; #if UNITY_2017_2_OR_NEWER using UnityEngine.XR; #else using XRDevice = UnityEngine.VR.VRDevice; #endif // UNITY_2017_2_OR_NEWER /// @cond namespace Gvr.Internal { class AndroidNativeHeadsetProvider : IHeadsetProvider { private IntPtr gvrContextPtr = XRDevice.GetNativePtr(); private GvrValue gvrValue = new GvrValue(); private gvr_event_header gvrEventHeader = new gvr_event_header(); private gvr_recenter_event_data gvrRecenterEventData = new gvr_recenter_event_data(); // |gvr_event| C struct is spec'd to be up to 512 bytes in size. private byte[] gvrEventBuffer = new byte[512]; private GCHandle gvrEventHandle; private IntPtr gvrEventPtr; private IntPtr gvrPropertiesPtr; private int supportsPositionalTracking = -1; /// Used only as a temporary placeholder to avoid allocations. /// Do not use this for storing state. private MutablePose3D transientRecenteredPose3d = new MutablePose3D(); private readonly Matrix4x4 MATRIX4X4_IDENTITY = Matrix4x4.identity; public bool SupportsPositionalTracking { get { if (supportsPositionalTracking < 0) { supportsPositionalTracking = gvr_is_feature_supported(gvrContextPtr, (int)gvr_feature.HeadPose6dof) ? 1 : 0; } return supportsPositionalTracking > 0; } } internal AndroidNativeHeadsetProvider() { gvrEventHandle = GCHandle.Alloc(gvrEventBuffer, GCHandleType.Pinned); gvrEventPtr = gvrEventHandle.AddrOfPinnedObject(); } ~AndroidNativeHeadsetProvider() { gvrEventHandle.Free(); } public void PollEventState(ref HeadsetState state) { GvrErrorType eventType; try { eventType = (GvrErrorType)gvr_poll_event(gvrContextPtr, gvrEventPtr); } catch (EntryPointNotFoundException) { Debug.LogError("GvrHeadset not supported by this version of Unity. " + "Support starts in 5.6.3p3 and 2017.1.1p1."); throw; } if (eventType == GvrErrorType.NoEventAvailable) { state.eventType = GvrEventType.Invalid; return; } Marshal.PtrToStructure(gvrEventPtr, gvrEventHeader); state.eventFlags = gvrEventHeader.flags; state.eventTimestampNs = gvrEventHeader.timestamp; state.eventType = (GvrEventType) gvrEventHeader.type; // Event data begins after header. IntPtr eventDataPtr = new IntPtr(gvrEventPtr.ToInt64() + Marshal.SizeOf(gvrEventHeader)); if (state.eventType == GvrEventType.Recenter) { Marshal.PtrToStructure(eventDataPtr, gvrRecenterEventData); _HandleRecenterEvent(ref state, gvrRecenterEventData); return; } } public bool TryGetFloorHeight(ref float floorHeight) { if (!_GvrGetProperty(gvr_property_type.TrackingFloorHeight, gvrValue)) { return false; } floorHeight = gvrValue.ToFloat(); return true; } public bool TryGetRecenterTransform(ref Vector3 position, ref Quaternion rotation) { if (!_GvrGetProperty(gvr_property_type.RecenterTransform, gvrValue)) { return false; } transientRecenteredPose3d.Set(gvrValue.ToMatrix4x4()); position = transientRecenteredPose3d.Position; rotation = transientRecenteredPose3d.Orientation; return true; } public bool TryGetSafetyRegionType(ref GvrSafetyRegionType safetyType) { if (!_GvrGetProperty(gvr_property_type.SafetyRegion, gvrValue)) { return false; } safetyType = (GvrSafetyRegionType) gvrValue.ToInt32(); return true; } public bool TryGetSafetyCylinderInnerRadius(ref float innerRadius) { if (!_GvrGetProperty(gvr_property_type.SafetyCylinderInnerRadius, gvrValue)) { return false; } innerRadius = gvrValue.ToFloat(); return true; } public bool TryGetSafetyCylinderOuterRadius(ref float outerRadius) { if (!_GvrGetProperty(gvr_property_type.SafetyCylinderOuterRadius, gvrValue)) { return false; } outerRadius = gvrValue.ToFloat(); return true; } #region PRIVATE_HELPERS /// Returns true if a property was available and retrieved. private bool _GvrGetProperty(gvr_property_type propertyType, GvrValue valueOut) { gvr_value_type valueType = GetPropertyValueType(propertyType); if (valueType == gvr_value_type.None) { Debug.LogError("Unknown gvr property " + propertyType + ". Unable to type check."); } if (gvrPropertiesPtr == IntPtr.Zero) { try { gvrPropertiesPtr = gvr_get_current_properties(gvrContextPtr); } catch (EntryPointNotFoundException) { Debug.LogError("GvrHeadset not supported by this version of Unity. " + "Support starts in 5.6.3p3 and 2017.1.1p1."); throw; } } if (gvrPropertiesPtr == IntPtr.Zero) { return false; } // Assumes that gvr_properties_get (C API) will only ever return // GvrErrorType.None or GvrErrorType.NoEventAvailable. bool success = (GvrErrorType.None == (GvrErrorType) gvr_properties_get(gvrPropertiesPtr, propertyType, valueOut.BufferPtr)); if (success) { valueOut.Parse(); success = (valueType == gvr_value_type.None || valueOut.TypeEnum == valueType); if (!success) { Debug.LogError("GvrGetProperty " + propertyType + " type mismatch, expected " + valueType + " got " + valueOut.TypeEnum); } } return success; } private void _HandleRecenterEvent(ref HeadsetState state, gvr_recenter_event_data eventData) { state.recenterEventType = (GvrRecenterEventType) eventData.recenter_event_type; state.recenterEventFlags = eventData.recenter_event_flags; Matrix4x4 poseTransform = MATRIX4X4_IDENTITY; float[] poseRaw = eventData.pose_transform; for (int i = 0; i < 4; i++) { int j = i * 4; Vector4 row = new Vector4(poseRaw[j], poseRaw[j + 1], poseRaw[j + 2], poseRaw[j + 3]); poseTransform.SetRow(i, row); } // Invert the matrix to go from row-major (GVR) to column-major (Unity), // and change from LHS to RHS coordinates. transientRecenteredPose3d.SetRightHanded(poseTransform.transpose); state.recenteredPosition = transientRecenteredPose3d.Position; state.recenteredRotation = transientRecenteredPose3d.Orientation; } #endregion // PRIVATE_HELPERS #region GVR_TYPE_HELPERS private gvr_value_type GetPropertyValueType(gvr_property_type propertyType) { gvr_value_type propType = gvr_value_type.None; switch(propertyType) { case gvr_property_type.TrackingFloorHeight: propType = gvr_value_type.Float; break; case gvr_property_type.RecenterTransform: propType = gvr_value_type.Mat4f; break; case gvr_property_type.SafetyRegion: propType = gvr_value_type.Int; break; case gvr_property_type.SafetyCylinderInnerRadius: propType = gvr_value_type.Float; break; case gvr_property_type.SafetyCylinderOuterRadius: propType = gvr_value_type.Float; break; } return propType; } /// Helper class to parse |gvr_value| structs into the varied data types it could contain. /// NOTE: Does NO type checking on value conversions. |_GvrGetProperty| checks types. private class GvrValue { private static readonly int HEADER_SIZE = Marshal.SizeOf(typeof(gvr_value_header)); private gvr_value_header valueHeader = new gvr_value_header(); // |gvr_value| C struct is spec'd to be up to 256 bytes in size. private byte[] buffer = new byte[256]; private IntPtr bufferPtr; private IntPtr valuePtr; private GCHandle bufferHandle; public GvrValue() { bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); bufferPtr = bufferHandle.AddrOfPinnedObject(); // Value portion starts after the header. valuePtr = new IntPtr(bufferPtr.ToInt64() + HEADER_SIZE); } ~GvrValue() { bufferHandle.Free(); } /// Gets the ptr to a buffer that can be used as an argument to |gvr_properties_get|. public IntPtr BufferPtr { get { return bufferPtr; } } public gvr_value_type TypeEnum { get { return valueHeader.value_type; } } /// Parse the header of the current buffer. This should be called after the contents of /// the buffer have been altered e.g. by a call to |gvr_properties_get|. public void Parse() { Marshal.PtrToStructure(bufferPtr, valueHeader); } public int ToInt32() { return BitConverter.ToInt32(buffer, HEADER_SIZE); } public long ToInt64() { return BitConverter.ToInt64(buffer, HEADER_SIZE); } public float ToFloat() { return BitConverter.ToSingle(buffer, HEADER_SIZE); } public double ToDouble() { return BitConverter.ToDouble(buffer, HEADER_SIZE); } public Vector2 ToVector2() { return (Vector2) Marshal.PtrToStructure(valuePtr, typeof(Vector2)); } public Vector3 ToVector3() { return (Vector3) Marshal.PtrToStructure(valuePtr, typeof(Vector3)); } public Vector4 ToVector4() { return (Vector4) Marshal.PtrToStructure(valuePtr, typeof(Vector4)); } public Quaternion ToQuaternion() { return (Quaternion) Marshal.PtrToStructure(valuePtr, typeof(Quaternion)); } public gvr_rectf ToGvrRectf() { return (gvr_rectf) Marshal.PtrToStructure(valuePtr, typeof(gvr_rectf)); } public gvr_recti ToGvrRecti() { return (gvr_recti) Marshal.PtrToStructure(valuePtr, typeof(gvr_recti)); } public Matrix4x4 ToMatrix4x4() { Matrix4x4 mat4 = (Matrix4x4) Marshal.PtrToStructure(valuePtr, typeof(Matrix4x4)); // Transpose the matrix from row-major (GVR) to column-major (Unity), // and change from LHS to RHS coordinates. return Pose3D.FlipHandedness(mat4.transpose); } } #endregion // GVR_TYPE_HELPERS #region GVR_NATIVE_STRUCTS [StructLayout(LayoutKind.Sequential)] private class gvr_recenter_event_data { internal int recenter_event_type; // gvr_recenter_event_type internal uint recenter_event_flags; // gvr_flags [MarshalAs(UnmanagedType.ByValArray, SizeConst=16)] internal float[] pose_transform = new float[16]; // gvr_mat4f = float[4][4] } [StructLayout(LayoutKind.Explicit)] private class gvr_event_header { [FieldOffset(0)] internal long timestamp; [FieldOffset(8)] internal int type; // gvr_event_type [FieldOffset(12)] internal int flags; // gvr_flags // Event specific data starts at offset 16. } [StructLayout(LayoutKind.Explicit)] private class gvr_value_header { [FieldOffset(0)] internal gvr_value_type value_type; [FieldOffset(4)] internal int flags; // gvr_flags // Value data starts at offset 8. } [StructLayout(LayoutKind.Sequential)] public struct gvr_sizei { internal int width; internal int height; } [StructLayout(LayoutKind.Sequential)] public struct gvr_recti { internal int left; internal int right; internal int bottom; internal int top; } [StructLayout(LayoutKind.Sequential)] public struct gvr_rectf { internal float left; internal float right; internal float bottom; internal float top; } #endregion // GVR_NATIVE_STRUCTS #region GVR_C_API private const string DLL_NAME = GvrActivityHelper.GVR_DLL_NAME; [DllImport(DLL_NAME)] private static extern bool gvr_is_feature_supported(IntPtr gvr_context, int feature); [DllImport(DLL_NAME)] private static extern int gvr_poll_event(IntPtr gvr_context, IntPtr event_out); [DllImport(DLL_NAME)] private static extern IntPtr gvr_get_current_properties(IntPtr gvr_context); [DllImport(DLL_NAME)] private static extern int gvr_properties_get( IntPtr gvr_properties, gvr_property_type property_type, IntPtr value_out); #endregion // GVR_C_API } } /// @endcond #endif // UNITY_ANDROID && !UNITY_EDITOR