532 lines
18 KiB
C#
532 lines
18 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Runtime.InteropServices;
|
||
using System.Text;
|
||
using Unity.XR.CoreUtils;
|
||
using UnityEditor;
|
||
using UnityEngine;
|
||
|
||
#if AR_FOUNDATION
|
||
using UnityEngine.XR.ARSubsystems;
|
||
#endif
|
||
using UnityEngine.XR.OpenXR;
|
||
using UnityEngine.XR.OpenXR.Features;
|
||
using Object = UnityEngine.Object;
|
||
|
||
#if UNITY_EDITOR
|
||
using UnityEditor.XR.OpenXR.Features;
|
||
#endif
|
||
|
||
namespace Unity.XR.OpenXR.Features.PICOSupport
|
||
{
|
||
#if UNITY_EDITOR
|
||
[OpenXRFeature(UiName = "OpenXR Passthrough",
|
||
Hidden = false,
|
||
BuildTargetGroups = new[] { UnityEditor.BuildTargetGroup.Android },
|
||
Company = "PICO",
|
||
OpenxrExtensionStrings = extensionString,
|
||
Version = "1.0.0",
|
||
FeatureId = featureId)]
|
||
#endif
|
||
public class PassthroughFeature : OpenXRFeatureBase
|
||
{
|
||
public const string featureId = "com.pico.openxr.feature.passthrough";
|
||
public const string extensionString = "XR_FB_passthrough";
|
||
public static bool isExtensionEnable = false;
|
||
public const int XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB = 256;
|
||
private static byte[] colorData;
|
||
private static uint Size = 0;
|
||
private static bool isInit = false;
|
||
private static bool isPause = false;
|
||
private static int _enableVideoSeeThrough=-1;
|
||
public static event Action<bool> EnableVideoSeeThroughAction;
|
||
[HideInInspector]
|
||
public static bool EnableVideoSeeThrough
|
||
{
|
||
get => _enableVideoSeeThrough==1;
|
||
set
|
||
{
|
||
if (value)
|
||
{
|
||
if (_enableVideoSeeThrough != 1)
|
||
{
|
||
_enableVideoSeeThrough = 1;
|
||
EnableSeeThroughManual(value);
|
||
|
||
if (EnableVideoSeeThroughAction != null)
|
||
{
|
||
EnableVideoSeeThroughAction(value);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if (_enableVideoSeeThrough == 1)
|
||
{
|
||
_enableVideoSeeThrough = 0;
|
||
EnableSeeThroughManual(value);
|
||
|
||
if (EnableVideoSeeThroughAction != null)
|
||
{
|
||
EnableVideoSeeThroughAction(value);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
public override void Initialize(IntPtr intPtr)
|
||
{
|
||
isExtensionEnable = _isExtensionEnable;
|
||
initialize(intPtr, xrInstance);
|
||
}
|
||
|
||
public override string GetExtensionString()
|
||
{
|
||
return extensionString;
|
||
}
|
||
|
||
public static void PassthroughStart()
|
||
{
|
||
passthroughStart();
|
||
isPause = false;
|
||
}
|
||
|
||
public static void PassthroughPause()
|
||
{
|
||
passthroughPause();
|
||
isPause = true;
|
||
}
|
||
|
||
public static bool EnableSeeThroughManual(bool value)
|
||
{
|
||
if (!isExtensionEnable)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!isInit)
|
||
{
|
||
isInit = initializePassthrough();
|
||
}
|
||
|
||
if (value)
|
||
{
|
||
createFullScreenLayer();
|
||
if (!isPause)
|
||
{
|
||
passthroughStart();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
passthroughPause();
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
public static void Destroy()
|
||
{
|
||
if (!isExtensionEnable)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Passthrough_Destroy();
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
Destroy();
|
||
}
|
||
|
||
private static void AllocateColorMapData(uint size)
|
||
{
|
||
if (colorData != null && size != colorData.Length)
|
||
{
|
||
Clear();
|
||
}
|
||
|
||
if (colorData == null)
|
||
{
|
||
colorData = new byte[size];
|
||
}
|
||
}
|
||
|
||
private static void Clear()
|
||
{
|
||
if (colorData != null)
|
||
{
|
||
colorData = null;
|
||
}
|
||
}
|
||
|
||
private static void WriteVector3ToColorMap(int colorIndex, ref Vector3 color)
|
||
{
|
||
for (int c = 0; c < 3; c++)
|
||
{
|
||
byte[] bytes = BitConverter.GetBytes(color[c]);
|
||
Buffer.BlockCopy(bytes, 0, colorData, colorIndex * 12 + c * 4, 4);
|
||
}
|
||
}
|
||
|
||
private static void WriteFloatToColorMap(int index, float value)
|
||
{
|
||
byte[] bytes = BitConverter.GetBytes(value);
|
||
Buffer.BlockCopy(bytes, 0, colorData, index * sizeof(float), sizeof(float));
|
||
}
|
||
|
||
private static void WriteColorToColorMap(int colorIndex, ref Color color)
|
||
{
|
||
for (int c = 0; c < 4; c++)
|
||
{
|
||
byte[] bytes = BitConverter.GetBytes(color[c]);
|
||
Buffer.BlockCopy(bytes, 0, colorData, colorIndex * 16 + c * 4, 4);
|
||
}
|
||
}
|
||
|
||
|
||
public static unsafe void SetBrightnessContrastSaturation(ref PassthroughStyle style, float brightness = 0.0f,
|
||
float contrast = 0.0f, float saturation = 0.0f)
|
||
{
|
||
style.enableColorMap = true;
|
||
style.TextureColorMapType = PassthroughColorMapType.BrightnessContrastSaturation;
|
||
Size = 3 * sizeof(float);
|
||
AllocateColorMapData(Size);
|
||
WriteFloatToColorMap(0, brightness);
|
||
|
||
WriteFloatToColorMap(1, contrast);
|
||
|
||
WriteFloatToColorMap(2, saturation);
|
||
fixed (byte* p = colorData)
|
||
{
|
||
style.TextureColorMapData = (IntPtr)p;
|
||
}
|
||
|
||
style.TextureColorMapDataSize = Size;
|
||
StringBuilder str = new StringBuilder();
|
||
for (int i = 0; i < Size; i++)
|
||
{
|
||
str.Append(colorData[i]);
|
||
}
|
||
|
||
Debug.Log("SetPassthroughStyle SetBrightnessContrastSaturation colorData:" + str);
|
||
}
|
||
|
||
public static unsafe void SetColorMapbyMonoToMono(ref PassthroughStyle style, int[] values)
|
||
{
|
||
if (values.Length != XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB)
|
||
throw new ArgumentException("Must provide exactly 256 values");
|
||
style.enableColorMap = true;
|
||
style.TextureColorMapType = PassthroughColorMapType.MonoToMono;
|
||
Size = XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB * 4;
|
||
AllocateColorMapData(Size);
|
||
Buffer.BlockCopy(values, 0, colorData, 0, (int)Size);
|
||
|
||
fixed (byte* p = colorData)
|
||
{
|
||
style.TextureColorMapData = (IntPtr)p;
|
||
}
|
||
|
||
style.TextureColorMapDataSize = Size;
|
||
}
|
||
|
||
public static unsafe void SetColorMapbyMonoToRgba(ref PassthroughStyle style, Color[] values)
|
||
{
|
||
if (values.Length != XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB)
|
||
throw new ArgumentException("Must provide exactly 256 colors");
|
||
|
||
style.TextureColorMapType = PassthroughColorMapType.MonoToRgba;
|
||
style.enableColorMap = true;
|
||
Size = XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB * 4 * 4;
|
||
|
||
AllocateColorMapData(Size);
|
||
|
||
for (int i = 0; i < XR_PASSTHROUGH_COLOR_MAP_MONO_SIZE_FB; i++)
|
||
{
|
||
WriteColorToColorMap(i, ref values[i]);
|
||
}
|
||
|
||
fixed (byte* p = colorData)
|
||
{
|
||
style.TextureColorMapData = (IntPtr)p;
|
||
}
|
||
|
||
style.TextureColorMapDataSize = Size;
|
||
}
|
||
|
||
public static _PassthroughStyle ToPassthroughStyle(PassthroughStyle c)
|
||
{
|
||
_PassthroughStyle mPassthroughStyle = new _PassthroughStyle();
|
||
mPassthroughStyle.enableEdgeColor = (uint)(c.enableEdgeColor ? 1 : 0);
|
||
mPassthroughStyle.enableColorMap = (uint)(c.enableColorMap ? 1 : 0);
|
||
mPassthroughStyle.TextureOpacityFactor = c.TextureOpacityFactor;
|
||
mPassthroughStyle.TextureColorMapType = c.TextureColorMapType;
|
||
mPassthroughStyle.TextureColorMapDataSize = c.TextureColorMapDataSize;
|
||
mPassthroughStyle.TextureColorMapData = c.TextureColorMapData;
|
||
mPassthroughStyle.EdgeColor = new Colorf()
|
||
{ r = c.EdgeColor.r, g = c.EdgeColor.g, b = c.EdgeColor.b, a = c.EdgeColor.a };
|
||
return mPassthroughStyle;
|
||
}
|
||
|
||
public static void SetPassthroughStyle(PassthroughStyle style)
|
||
{
|
||
setPassthroughStyle(ToPassthroughStyle(style));
|
||
}
|
||
|
||
public static bool IsPassthroughSupported()
|
||
{
|
||
return isPassthroughSupported();
|
||
}
|
||
|
||
|
||
public static unsafe bool CreateTriangleMesh(int id, Vector3[] vertices, int[] triangles,
|
||
GeometryInstanceTransform transform)
|
||
{
|
||
if (vertices == null || triangles == null || vertices.Length == 0 || triangles.Length == 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
if (!isInit)
|
||
{
|
||
isInit = initializePassthrough();
|
||
}
|
||
|
||
int vertexCount = vertices.Length;
|
||
int triangleCount = triangles.Length;
|
||
|
||
Size = (uint)vertexCount * 3 * 4;
|
||
|
||
AllocateColorMapData(Size);
|
||
|
||
for (int i = 0; i < vertexCount; i++)
|
||
{
|
||
WriteVector3ToColorMap(i, ref vertices[i]);
|
||
}
|
||
|
||
IntPtr vertexDataPtr = IntPtr.Zero;
|
||
|
||
fixed (byte* p = colorData)
|
||
{
|
||
vertexDataPtr = (IntPtr)p;
|
||
}
|
||
|
||
StringBuilder str = new StringBuilder();
|
||
for (int i = 0; i < 3 * 4; i++)
|
||
{
|
||
str.Append(colorData[i]);
|
||
}
|
||
|
||
Debug.Log("CreateTriangleMesh vertexDataPtr colorData:" + str);
|
||
str.Clear();
|
||
|
||
Size = (uint)triangleCount * 4;
|
||
AllocateColorMapData(Size);
|
||
Buffer.BlockCopy(triangles, 0, colorData, 0, (int)Size);
|
||
IntPtr triangleDataPtr = IntPtr.Zero;
|
||
fixed (byte* p = colorData)
|
||
{
|
||
triangleDataPtr = (IntPtr)p;
|
||
}
|
||
|
||
for (int i = 0; i < colorData.Length; i++)
|
||
{
|
||
str.Append(colorData[i]);
|
||
}
|
||
|
||
// Debug.Log("CreateTriangleMesh triangleDataPtr colorData:" + str);
|
||
//
|
||
// Debug.Log("CreateTriangleMesh vertexDataPtr=" + vertexDataPtr + " vertexCount=" + vertexCount);
|
||
// Debug.Log("CreateTriangleMesh triangleDataPtr=" + triangleDataPtr + " triangleCount=" + triangleCount);
|
||
|
||
XrResult result =
|
||
createTriangleMesh(id, vertexDataPtr, vertexCount, triangleDataPtr, triangleCount, transform);
|
||
Clear();
|
||
if (result == XrResult.Success)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
public static void UpdateMeshTransform(int id, GeometryInstanceTransform transform)
|
||
{
|
||
updatePassthroughMeshTransform(id, transform);
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
/// <summary>
|
||
/// Validation Rules for ARCameraFeature.
|
||
/// </summary>
|
||
protected override void GetValidationChecks(List<ValidationRule> rules, BuildTargetGroup targetGroup)
|
||
{
|
||
var AdditionalRules = new ValidationRule[]
|
||
{
|
||
new ValidationRule(this)
|
||
{
|
||
message = "Passthrough requires Camera clear flags set to solid color with alpha value zero.",
|
||
checkPredicate = () =>
|
||
{
|
||
|
||
var xrOrigin = FindObjectsOfType<XROrigin>();
|
||
|
||
if (xrOrigin != null && xrOrigin.Length > 0)
|
||
{
|
||
if (!xrOrigin[0].enabled) return true;
|
||
}
|
||
else
|
||
{
|
||
return true;
|
||
}
|
||
|
||
var camera = xrOrigin[0].Camera;
|
||
if (camera == null) return true;
|
||
|
||
return camera.clearFlags == CameraClearFlags.SolidColor && Mathf.Approximately(camera.backgroundColor.a, 0);
|
||
},
|
||
fixItAutomatic = true,
|
||
fixItMessage = "Set your XR Origin camera's Clear Flags to solid color with alpha value zero.",
|
||
fixIt = () =>
|
||
{
|
||
var xrOrigin = FindObjectsOfType<XROrigin>();
|
||
if (xrOrigin!=null&&xrOrigin.Length>0)
|
||
{
|
||
if (xrOrigin[0].enabled)
|
||
{
|
||
var camera = xrOrigin[0].Camera;
|
||
if (camera != null )
|
||
{
|
||
camera.clearFlags = CameraClearFlags.SolidColor;
|
||
Color clearColor = camera.backgroundColor;
|
||
clearColor.a = 0;
|
||
camera.backgroundColor = clearColor;
|
||
}
|
||
}
|
||
}
|
||
|
||
},
|
||
error = false
|
||
}
|
||
};
|
||
|
||
rules.AddRange(AdditionalRules);
|
||
}
|
||
#endif
|
||
|
||
#if AR_FOUNDATION
|
||
public bool isCameraSubsystem=true;
|
||
static List<XRCameraSubsystemDescriptor> s_CameraDescriptors = new List<XRCameraSubsystemDescriptor>();
|
||
protected override void OnSubsystemCreate()
|
||
{
|
||
base.OnSubsystemCreate();
|
||
if (isCameraSubsystem)
|
||
{
|
||
CreateSubsystem<XRCameraSubsystemDescriptor, XRCameraSubsystem>(
|
||
s_CameraDescriptors,
|
||
PICOCameraSubsystem.k_SubsystemId);
|
||
}
|
||
|
||
}
|
||
protected override void OnSubsystemStart()
|
||
{
|
||
if (isCameraSubsystem)
|
||
{
|
||
StartSubsystem<XRCameraSubsystem>();
|
||
}
|
||
}
|
||
protected override void OnSubsystemStop()
|
||
{
|
||
if (isCameraSubsystem)
|
||
{
|
||
StopSubsystem<XRCameraSubsystem>();
|
||
}
|
||
}
|
||
protected override void OnSubsystemDestroy()
|
||
{
|
||
if (isCameraSubsystem)
|
||
{
|
||
DestroySubsystem<XRCameraSubsystem>();
|
||
}
|
||
}
|
||
|
||
#endif
|
||
protected override void OnSessionStateChange(int oldState, int newState)
|
||
{
|
||
base.OnSessionStateChange(oldState, newState);
|
||
if (newState == 1)
|
||
{
|
||
#if AR_FOUNDATION
|
||
if (isCameraSubsystem)
|
||
{
|
||
StopSubsystem<XRCameraSubsystem>();
|
||
}else{
|
||
if (_enableVideoSeeThrough!=-1)
|
||
{
|
||
EnableSeeThroughManual(false);
|
||
}
|
||
}
|
||
#else
|
||
if (_enableVideoSeeThrough!=-1)
|
||
{
|
||
EnableSeeThroughManual(false);
|
||
}
|
||
#endif
|
||
}
|
||
else if (newState == 5)
|
||
{
|
||
#if AR_FOUNDATION
|
||
if (isCameraSubsystem)
|
||
{
|
||
StartSubsystem<XRCameraSubsystem>();
|
||
}else{
|
||
if (_enableVideoSeeThrough!=-1)
|
||
{
|
||
EnableSeeThroughManual(EnableVideoSeeThrough);
|
||
}
|
||
}
|
||
#else
|
||
if (_enableVideoSeeThrough!=-1)
|
||
{
|
||
EnableSeeThroughManual(EnableVideoSeeThrough);
|
||
}
|
||
|
||
#endif
|
||
}
|
||
}
|
||
private const string ExtLib = "openxr_pico";
|
||
|
||
[DllImport(ExtLib, EntryPoint = "PICO_initialize_Passthrough", CallingConvention = CallingConvention.Cdecl)]
|
||
private static extern void initialize(IntPtr xrGetInstanceProcAddr, ulong xrInstance);
|
||
|
||
[DllImport(ExtLib, EntryPoint = "PICO_InitializePassthrough", CallingConvention = CallingConvention.Cdecl)]
|
||
private static extern bool initializePassthrough();
|
||
|
||
[DllImport(ExtLib, EntryPoint = "PICO_CreateFullScreenLayer", CallingConvention = CallingConvention.Cdecl)]
|
||
private static extern bool createFullScreenLayer();
|
||
|
||
[DllImport(ExtLib, EntryPoint = "PICO_PassthroughStart", CallingConvention = CallingConvention.Cdecl)]
|
||
private static extern void passthroughStart();
|
||
|
||
[DllImport(ExtLib, EntryPoint = "PICO_PassthroughPause", CallingConvention = CallingConvention.Cdecl)]
|
||
private static extern void passthroughPause();
|
||
|
||
[DllImport(ExtLib, EntryPoint = "PICO_SetPassthroughStyle", CallingConvention = CallingConvention.Cdecl)]
|
||
private static extern void setPassthroughStyle(_PassthroughStyle style);
|
||
|
||
[DllImport(ExtLib, EntryPoint = "PICO_IsPassthroughSupported", CallingConvention = CallingConvention.Cdecl)]
|
||
private static extern bool isPassthroughSupported();
|
||
|
||
[DllImport(ExtLib, EntryPoint = "PICO_Passthrough_Destroy", CallingConvention = CallingConvention.Cdecl)]
|
||
private static extern void Passthrough_Destroy();
|
||
|
||
[DllImport(ExtLib, EntryPoint = "PICO_CreateTriangleMesh", CallingConvention = CallingConvention.Cdecl)]
|
||
private static extern XrResult createTriangleMesh(int id, IntPtr vertices, int vertexCount, IntPtr triangles,
|
||
int triangleCount, GeometryInstanceTransform transform);
|
||
|
||
[DllImport(ExtLib, EntryPoint = "PICO_UpdatePassthroughMeshTransform",
|
||
CallingConvention = CallingConvention.Cdecl)]
|
||
private static extern void updatePassthroughMeshTransform(int id, GeometryInstanceTransform transform);
|
||
}
|
||
} |