FittsLaw/Assets/GoogleVR/Scripts/Headset/Internal/HeadsetProviders/AndroidNativeHeadsetProvider.cs
2018-10-08 23:54:11 -04:00

387 lines
13 KiB
C#

// 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