387 lines
13 KiB
C#
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
|