1853 lines
56 KiB
C#
1853 lines
56 KiB
C#
// "VIVE SDK
|
||
// © 2020 HTC Corporation. All Rights Reserved.
|
||
//
|
||
// Unless otherwise required by copyright law and practice,
|
||
// upon the execution of HTC SDK license agreement,
|
||
// HTC grants you access to and use of the VIVE SDK(s).
|
||
// You shall fully comply with all of HTC’s SDK license agreement terms and
|
||
// conditions signed by you and all SDK and API requirements,
|
||
// specifications, and documentation provided by HTC to You."
|
||
|
||
using System;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using UnityEngine.XR;
|
||
using UnityEngine.XR.OpenXR;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
using VIVE.OpenXR.CompositionLayer;
|
||
|
||
namespace VIVE.OpenXR.CompositionLayer
|
||
{
|
||
public class CompositionLayer : MonoBehaviour
|
||
{
|
||
#if UNITY_EDITOR
|
||
[SerializeField]
|
||
public bool isPreviewingCylinder = false;
|
||
[SerializeField]
|
||
public bool isPreviewingQuad = false;
|
||
[SerializeField]
|
||
public GameObject generatedPreview = null;
|
||
|
||
public const string CylinderPreviewName = "CylinderPreview";
|
||
public const string QuadPreviewName = "QuadPreview";
|
||
#endif
|
||
public const string CylinderUnderlayMeshName = "Underlay Alpha Mesh (Cylinder)";
|
||
public const string QuadUnderlayMeshName = "Underlay Alpha Mesh (Quad)";
|
||
public const string FallbackMeshName = "FallbackMeshGO";
|
||
|
||
[SerializeField]
|
||
public LayerType layerType = LayerType.Overlay;
|
||
|
||
[SerializeField]
|
||
public uint compositionDepth = 0;
|
||
|
||
[SerializeField]
|
||
public LayerShape layerShape = LayerShape.Quad;
|
||
|
||
[SerializeField]
|
||
public Visibility layerVisibility = Visibility.Both;
|
||
|
||
[SerializeField]
|
||
[Min(0.001f)]
|
||
private float m_QuadWidth = 1f;
|
||
public float quadWidth { get { return m_QuadWidth; } }
|
||
#if UNITY_EDITOR
|
||
public float QuadWidth { get { return m_QuadWidth; } set { m_QuadWidth = value; } }
|
||
#endif
|
||
|
||
[SerializeField]
|
||
[Min(0.001f)]
|
||
private float m_QuadHeight = 1f;
|
||
public float quadHeight { get { return m_QuadHeight; } }
|
||
#if UNITY_EDITOR
|
||
public float QuadHeight { get { return m_QuadHeight; } set { m_QuadHeight = value; } }
|
||
#endif
|
||
|
||
[SerializeField]
|
||
[Min(0.001f)]
|
||
private float m_CylinderHeight = 1f;
|
||
public float cylinderHeight { get { return m_CylinderHeight; } }
|
||
#if UNITY_EDITOR
|
||
public float CylinderHeight { get { return m_CylinderHeight; } set { m_CylinderHeight = value; } }
|
||
#endif
|
||
|
||
[SerializeField]
|
||
[Min(0.001f)]
|
||
private float m_CylinderArcLength = 1f;
|
||
public float cylinderArcLength { get { return m_CylinderArcLength; } }
|
||
#if UNITY_EDITOR
|
||
public float CylinderArcLength { get { return m_CylinderArcLength; } set { m_CylinderArcLength = value; } }
|
||
#endif
|
||
|
||
[SerializeField]
|
||
[Min(0.001f)]
|
||
private float m_CylinderRadius = 1f;
|
||
public float cylinderRadius { get { return m_CylinderRadius; } }
|
||
#if UNITY_EDITOR
|
||
public float CylinderRadius { get { return m_CylinderRadius; } set { m_CylinderRadius = value; } }
|
||
#endif
|
||
|
||
[SerializeField]
|
||
[Range(0f, 360f)]
|
||
private float m_CylinderAngleOfArc = 180f;
|
||
public float cylinderAngleOfArc { get { return m_CylinderAngleOfArc; } }
|
||
#if UNITY_EDITOR
|
||
public float CylinderAngleOfArc { get { return m_CylinderAngleOfArc; } set { m_CylinderAngleOfArc = value; } }
|
||
#endif
|
||
|
||
#if UNITY_EDITOR
|
||
[SerializeField]
|
||
public CylinderLayerParamLockMode lockMode = CylinderLayerParamLockMode.ArcLength;
|
||
#endif
|
||
|
||
[SerializeField]
|
||
public bool isDynamicLayer = false;
|
||
|
||
[SerializeField]
|
||
public bool applyColorScaleBias = false;
|
||
|
||
[SerializeField]
|
||
public Color colorScale = Color.white;
|
||
|
||
[SerializeField]
|
||
public Color colorBias = Color.clear;
|
||
|
||
[SerializeField]
|
||
public bool isProtectedSurface = false;
|
||
|
||
[SerializeField]
|
||
public Texture texture = null;
|
||
|
||
[SerializeField]
|
||
private uint renderPriority = 0;
|
||
public uint GetRenderPriority() { return renderPriority; }
|
||
public void SetRenderPriority(uint newRenderPriority)
|
||
{
|
||
renderPriority = newRenderPriority;
|
||
isRenderPriorityChanged = true;
|
||
CompositionLayerManager.GetInstance().SubscribeToLayerManager(this);
|
||
}
|
||
public bool isRenderPriorityChanged
|
||
{
|
||
get; private set;
|
||
}
|
||
|
||
[SerializeField]
|
||
public GameObject trackingOrigin = null;
|
||
|
||
public GameObject generatedUnderlayMesh = null;
|
||
private MeshRenderer generatedUnderlayMeshRenderer = null;
|
||
private MeshFilter generatedUnderlayMeshFilter = null;
|
||
|
||
public GameObject generatedFallbackMesh = null;
|
||
private MeshRenderer generatedFallbackMeshRenderer = null;
|
||
private MeshFilter generatedFallbackMeshFilter = null;
|
||
|
||
private LayerTextures layerTextures;
|
||
private Material texture2DBlitMaterial;
|
||
|
||
private GameObject compositionLayerPlaceholderPrefabGO = null;
|
||
|
||
public readonly float angleOfArcLowerLimit = 1f;
|
||
public readonly float angleOfArcUpperLimit = 360f;
|
||
|
||
private LayerShape previousLayerShape = LayerShape.Quad;
|
||
private float previousQuadWidth = 1f;
|
||
private float previousQuadHeight = 1f;
|
||
private float previousCylinderHeight = 1f;
|
||
private float previousCylinderArcLength = 1f;
|
||
private float previousCylinderRadius = 1f;
|
||
private float previousAngleOfArc = 180f;
|
||
private Texture previousTexture = null;
|
||
private bool previousIsDynamicLayer = false;
|
||
|
||
private int layerID; //For native
|
||
private bool isHeadLock = false;
|
||
private bool InitStatus = false;
|
||
private bool isInitializationComplete = false;
|
||
private bool reinitializationNeeded = false;
|
||
private bool isOnBeforeRenderSubscribed = false;
|
||
private bool isLayerReadyForSubmit = false;
|
||
private bool isLinear = false;
|
||
private bool isAutoFallbackActive = false;
|
||
private bool placeholderGenerated = false;
|
||
private static bool isSynchronized = false;
|
||
private static RenderThreadSynchronizer synchronizer;
|
||
private Camera hmd;
|
||
|
||
private ViveCompositionLayer compositionLayerFeature = null;
|
||
|
||
private const string LOG_TAG = "VIVE_CompositionLayer";
|
||
static void DEBUG(string msg) { Debug.Log(LOG_TAG + " " + msg); }
|
||
static void WARNING(string msg) { Debug.LogWarning(LOG_TAG + " " + msg); }
|
||
static void ERROR(string msg) { Debug.LogError(LOG_TAG + " " + msg); }
|
||
|
||
#region Composition Layer Lifecycle
|
||
private bool CompositionLayerInit()
|
||
{
|
||
if (isInitializationComplete)
|
||
{
|
||
//DEBUG("CompositionLayerInit: Already initialized.");
|
||
return true;
|
||
}
|
||
|
||
if (texture == null)
|
||
{
|
||
ERROR("CompositionLayerInit: Source Texture not found, abort init.");
|
||
return false;
|
||
}
|
||
|
||
DEBUG("CompositionLayerInit");
|
||
|
||
uint textureWidth = (uint)texture.width;
|
||
uint textureHeight = (uint)texture.height;
|
||
|
||
CompositionLayerRenderThreadSyncObject ObtainLayerSwapchainSyncObject = new CompositionLayerRenderThreadSyncObject(
|
||
(taskQueue) =>
|
||
{
|
||
lock (taskQueue)
|
||
{
|
||
CompositionLayerRenderThreadTask task = (CompositionLayerRenderThreadTask)taskQueue.Dequeue();
|
||
|
||
//Enumerate Swapchain formats
|
||
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||
|
||
uint imageCount;
|
||
|
||
GraphicsAPI graphicsAPI = GraphicsAPI.GLES3;
|
||
|
||
switch(SystemInfo.graphicsDeviceType)
|
||
{
|
||
case UnityEngine.Rendering.GraphicsDeviceType.OpenGLES3:
|
||
graphicsAPI = GraphicsAPI.GLES3;
|
||
break;
|
||
case UnityEngine.Rendering.GraphicsDeviceType.Vulkan:
|
||
graphicsAPI = GraphicsAPI.Vulkan;
|
||
break;
|
||
default:
|
||
ERROR("Unsupported Graphics API, aborting init process.");
|
||
return;
|
||
}
|
||
|
||
layerID = compositionLayerFeature.CompositionLayer_Init(textureWidth, textureHeight, graphicsAPI, isDynamicLayer, isProtectedSurface, out imageCount);
|
||
|
||
if (layerID != 0)
|
||
{
|
||
DEBUG("Init completed, ID: " + layerID + ", Image Count: " + imageCount);
|
||
layerTextures = new LayerTextures(imageCount);
|
||
InitStatus = true;
|
||
}
|
||
|
||
taskQueue.Release(task);
|
||
}
|
||
});
|
||
|
||
CompositionLayerRenderThreadTask.IssueObtainSwapchainEvent(ObtainLayerSwapchainSyncObject);
|
||
|
||
previousLayerShape = layerShape;
|
||
previousQuadWidth = m_QuadWidth;
|
||
previousQuadHeight = m_QuadHeight;
|
||
previousCylinderHeight = m_CylinderHeight;
|
||
previousCylinderArcLength = m_CylinderArcLength;
|
||
previousCylinderRadius = m_CylinderRadius;
|
||
previousAngleOfArc = m_CylinderAngleOfArc;
|
||
previousTexture = texture;
|
||
previousIsDynamicLayer = isDynamicLayer;
|
||
|
||
return true;
|
||
}
|
||
|
||
private bool textureAcquired = false;
|
||
private bool textureAcquiredOnce = false;
|
||
XrOffset2Di offset = new XrOffset2Di();
|
||
XrExtent2Di extent = new XrExtent2Di();
|
||
XrRect2Di rect = new XrRect2Di();
|
||
private bool SetLayerTexture()
|
||
{
|
||
if (!isInitializationComplete || !isSynchronized) return false;
|
||
|
||
if (texture != null) //check for texture size change
|
||
{
|
||
if (TextureParamsChanged())
|
||
{
|
||
//Destroy queues
|
||
DEBUG("SetLayerTexture: Texture params changed, need to re-init queues. layerID: " + layerID);
|
||
DestroyCompositionLayer();
|
||
reinitializationNeeded = true;
|
||
return false;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
ERROR("SetLayerTexture: No texture found. layerID: " + layerID);
|
||
return false;
|
||
}
|
||
|
||
if (isDynamicLayer || (!isDynamicLayer && !textureAcquiredOnce))
|
||
{
|
||
//Get available texture id
|
||
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||
uint currentImageIndex;
|
||
|
||
IntPtr newTextureID = compositionLayerFeature.CompositionLayer_GetTexture(layerID, out currentImageIndex);
|
||
|
||
textureAcquired = true;
|
||
textureAcquiredOnce = true;
|
||
|
||
if (newTextureID == IntPtr.Zero)
|
||
{
|
||
ERROR("SetLayerTexture: Invalid Texture ID");
|
||
if (compositionLayerFeature.CompositionLayer_ReleaseTexture(layerID))
|
||
{
|
||
textureAcquired = false;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool textureIDUpdated = false;
|
||
layerTextures.currentAvailableTextureIndex = currentImageIndex;
|
||
IntPtr currentTextureID = layerTextures.GetCurrentAvailableTextureID();
|
||
if (currentTextureID == IntPtr.Zero || currentTextureID != newTextureID)
|
||
{
|
||
DEBUG("SetLayerTexture: Update Texture ID. layerID: " + layerID);
|
||
layerTextures.SetCurrentAvailableTextureID(newTextureID);
|
||
textureIDUpdated = true;
|
||
}
|
||
|
||
if (layerTextures.GetCurrentAvailableTextureID() == IntPtr.Zero)
|
||
{
|
||
ERROR("SetLayerTexture: Failed to get texture.");
|
||
return false;
|
||
}
|
||
|
||
// Create external texture
|
||
if (layerTextures.GetCurrentAvailableExternalTexture() == null || textureIDUpdated)
|
||
{
|
||
DEBUG("SetLayerTexture: Create External Texture.");
|
||
layerTextures.SetCurrentAvailableExternalTexture(Texture2D.CreateExternalTexture(texture.width, texture.height, TextureFormat.RGBA32, false, isLinear, layerTextures.GetCurrentAvailableTextureID()));
|
||
}
|
||
|
||
if (layerTextures.externalTextures[layerTextures.currentAvailableTextureIndex] == null)
|
||
{
|
||
ERROR("SetLayerTexture: Create External Texture Failed.");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
//Set Texture Content
|
||
|
||
bool isContentSet = layerTextures.textureContentSet[layerTextures.currentAvailableTextureIndex];
|
||
|
||
if (!isDynamicLayer && isContentSet)
|
||
{
|
||
return true;
|
||
}
|
||
|
||
int currentTextureWidth = layerTextures.GetCurrentAvailableExternalTexture().width;
|
||
int currentTextureHeight = layerTextures.GetCurrentAvailableExternalTexture().height;
|
||
|
||
//Set Texture Layout
|
||
offset.x = 0;
|
||
offset.y = 0;
|
||
extent.width = (int)currentTextureWidth;
|
||
extent.height = (int)currentTextureHeight;
|
||
rect.offset = offset;
|
||
rect.extent = extent;
|
||
|
||
layerTextures.textureLayout = rect;
|
||
|
||
//Blit and copy texture
|
||
RenderTexture srcTexture = texture as RenderTexture;
|
||
int msaaSamples = 1;
|
||
if (srcTexture != null)
|
||
{
|
||
msaaSamples = srcTexture.antiAliasing;
|
||
}
|
||
|
||
Material currentBlitMat = texture2DBlitMaterial;
|
||
|
||
RenderTextureDescriptor rtDescriptor = new RenderTextureDescriptor(currentTextureWidth, currentTextureHeight, RenderTextureFormat.ARGB32, 0);
|
||
rtDescriptor.msaaSamples = msaaSamples;
|
||
rtDescriptor.autoGenerateMips = false;
|
||
rtDescriptor.useMipMap = false;
|
||
rtDescriptor.sRGB = false;
|
||
|
||
RenderTexture blitTempRT = RenderTexture.GetTemporary(rtDescriptor);
|
||
if (!blitTempRT.IsCreated())
|
||
{
|
||
blitTempRT.Create();
|
||
}
|
||
blitTempRT.DiscardContents();
|
||
|
||
Texture dstTexture = layerTextures.GetCurrentAvailableExternalTexture();
|
||
Graphics.Blit(texture, blitTempRT, currentBlitMat);
|
||
Graphics.CopyTexture(blitTempRT, 0, 0, dstTexture, 0, 0);
|
||
|
||
//DEBUG("Blit and CopyTexture complete.");
|
||
|
||
if (blitTempRT != null)
|
||
{
|
||
RenderTexture.ReleaseTemporary(blitTempRT);
|
||
}
|
||
else
|
||
{
|
||
ERROR("blitTempRT not found");
|
||
return false;
|
||
}
|
||
|
||
layerTextures.textureContentSet[layerTextures.currentAvailableTextureIndex] = true;
|
||
|
||
bool releaseTextureResult = compositionLayerFeature.CompositionLayer_ReleaseTexture(layerID);
|
||
if (releaseTextureResult)
|
||
{
|
||
textureAcquired = false;
|
||
}
|
||
|
||
return releaseTextureResult;
|
||
}
|
||
|
||
private void GetCompositionLayerPose(ref XrPosef pose) //Call at onBeforeRender
|
||
{
|
||
//Check if overlay is child of hmd transform i.e. head-lock
|
||
Transform currentTransform = transform;
|
||
isHeadLock = false;
|
||
while (currentTransform != null)
|
||
{
|
||
if (currentTransform == hmd.transform) //Overlay is child of hmd transform
|
||
{
|
||
isHeadLock = true;
|
||
break;
|
||
}
|
||
currentTransform = currentTransform.parent;
|
||
}
|
||
|
||
if (isHeadLock)
|
||
{
|
||
//Calculate Layer Rotation and Position relative to Camera
|
||
Vector3 relativePosition = hmd.transform.InverseTransformPoint(transform.position);
|
||
Quaternion relativeRotation = Quaternion.Inverse(hmd.transform.rotation).normalized * transform.rotation.normalized;
|
||
|
||
UnityToOpenXRConversionHelper.GetOpenXRVector(relativePosition, ref pose.position);
|
||
UnityToOpenXRConversionHelper.GetOpenXRQuaternion(relativeRotation.normalized, ref pose.orientation);
|
||
}
|
||
else
|
||
{
|
||
Vector3 trackingSpacePosition = transform.position;
|
||
Quaternion trackingSpaceRotation = transform.rotation;
|
||
|
||
if (trackingOrigin != null) //Apply origin correction to the layer pose
|
||
{
|
||
Matrix4x4 trackingSpaceOriginTRS = Matrix4x4.TRS(trackingOrigin.transform.position, trackingOrigin.transform.rotation, Vector3.one);
|
||
Matrix4x4 worldSpaceLayerPoseTRS = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one);
|
||
|
||
Matrix4x4 trackingSpaceLayerPoseTRS = trackingSpaceOriginTRS.inverse * worldSpaceLayerPoseTRS;
|
||
|
||
trackingSpacePosition = trackingSpaceLayerPoseTRS.GetColumn(3); //4th Column of TRS Matrix is the position
|
||
trackingSpaceRotation = Quaternion.LookRotation(trackingSpaceLayerPoseTRS.GetColumn(2), trackingSpaceLayerPoseTRS.GetColumn(1));
|
||
}
|
||
UnityToOpenXRConversionHelper.GetOpenXRVector(trackingSpacePosition, ref pose.position);
|
||
UnityToOpenXRConversionHelper.GetOpenXRQuaternion(trackingSpaceRotation.normalized, ref pose.orientation);
|
||
}
|
||
}
|
||
|
||
bool enabledColorScaleBiasInShader = false;
|
||
XrCompositionLayerColorScaleBiasKHR CompositionLayerParamsColorScaleBias = new XrCompositionLayerColorScaleBiasKHR();
|
||
private void SubmitCompositionLayer() //Call at onBeforeRender
|
||
{
|
||
if (!isInitializationComplete && !isLayerReadyForSubmit) return;
|
||
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||
|
||
if (isInitializationComplete && isSynchronized)
|
||
{
|
||
ViveCompositionLayerColorScaleBias compositionLayerColorScaleBias = OpenXRSettings.Instance.GetFeature<ViveCompositionLayerColorScaleBias>();
|
||
if (applyColorScaleBias && compositionLayerColorScaleBias.ColorScaleBiasExtensionEnabled)
|
||
{
|
||
if (layerType == LayerType.Underlay)
|
||
{
|
||
if (!enabledColorScaleBiasInShader)
|
||
{
|
||
if (generatedUnderlayMeshRenderer != null && generatedUnderlayMeshRenderer.material != null)
|
||
{
|
||
generatedUnderlayMeshRenderer.material.EnableKeyword("COLOR_SCALE_BIAS_ENABLED");
|
||
enabledColorScaleBiasInShader = true;
|
||
}
|
||
}
|
||
|
||
if (generatedUnderlayMeshRenderer != null && generatedUnderlayMeshRenderer.material != null)
|
||
{
|
||
generatedUnderlayMeshRenderer.material.SetColor("_ColorScale", colorScale);
|
||
generatedUnderlayMeshRenderer.material.SetColor("_ColorBias", colorBias);
|
||
}
|
||
}
|
||
else if (enabledColorScaleBiasInShader) //Disable if not underlay
|
||
{
|
||
generatedUnderlayMeshRenderer.material.DisableKeyword("COLOR_SCALE_BIAS_ENABLED");
|
||
enabledColorScaleBiasInShader = false;
|
||
}
|
||
|
||
CompositionLayerParamsColorScaleBias.type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_COLOR_SCALE_BIAS_KHR;
|
||
|
||
UnityToOpenXRConversionHelper.GetOpenXRColor4f(colorScale, ref CompositionLayerParamsColorScaleBias.colorScale);
|
||
UnityToOpenXRConversionHelper.GetOpenXRColor4f(colorBias, ref CompositionLayerParamsColorScaleBias.colorBias);
|
||
|
||
compositionLayerColorScaleBias.Submit_CompositionLayerColorBias(CompositionLayerParamsColorScaleBias, layerID);
|
||
}
|
||
else if (enabledColorScaleBiasInShader) //Disable if color scale bias is no longer active
|
||
{
|
||
generatedUnderlayMeshRenderer.material.DisableKeyword("COLOR_SCALE_BIAS_ENABLED");
|
||
enabledColorScaleBiasInShader = false;
|
||
}
|
||
|
||
|
||
switch (layerShape)
|
||
{
|
||
default:
|
||
case LayerShape.Quad:
|
||
compositionLayerFeature.Submit_CompositionLayerQuad(AssignCompositionLayerParamsQuad(), (OpenXR.CompositionLayer.LayerType)layerType, compositionDepth, layerID);
|
||
break;
|
||
case LayerShape.Cylinder:
|
||
ViveCompositionLayerCylinder compositionLayerCylinderFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayerCylinder>();
|
||
if (compositionLayerCylinderFeature != null && compositionLayerCylinderFeature.CylinderExtensionEnabled)
|
||
{
|
||
compositionLayerCylinderFeature.Submit_CompositionLayerCylinder(AssignCompositionLayerParamsCylinder(), (OpenXR.CompositionLayer.LayerType)layerType, compositionDepth, layerID);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
|
||
public delegate void OnDestroyCompositionLayer();
|
||
public event OnDestroyCompositionLayer OnDestroyCompositionLayerDelegate = null;
|
||
|
||
private void DestroyCompositionLayer()
|
||
{
|
||
if (!isInitializationComplete || layerTextures == null)
|
||
{
|
||
DEBUG("DestroyCompositionLayer: Layer already destroyed/not initialized.");
|
||
return;
|
||
}
|
||
|
||
DEBUG("DestroyCompositionLayer");
|
||
|
||
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||
|
||
if (textureAcquired)
|
||
{
|
||
DEBUG("DestroyCompositionLayer: textureAcquired, releasing.");
|
||
textureAcquired = !compositionLayerFeature.CompositionLayer_ReleaseTexture(layerID);
|
||
}
|
||
|
||
CompositionLayerRenderThreadSyncObject DestroyLayerSwapchainSyncObject = new CompositionLayerRenderThreadSyncObject(
|
||
(taskQueue) =>
|
||
{
|
||
lock (taskQueue)
|
||
{
|
||
CompositionLayerRenderThreadTask task = (CompositionLayerRenderThreadTask)taskQueue.Dequeue();
|
||
|
||
//Enumerate Swapchain formats
|
||
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||
|
||
if (!compositionLayerFeature.CompositionLayer_Destroy(task.layerID))
|
||
{
|
||
ERROR("estroyCompositionLayer: CompositionLayer_Destroy failed.");
|
||
}
|
||
|
||
taskQueue.Release(task);
|
||
}
|
||
});
|
||
|
||
CompositionLayerRenderThreadTask.IssueDestroySwapchainEvent(DestroyLayerSwapchainSyncObject, layerID);
|
||
|
||
InitStatus = false;
|
||
isLayerReadyForSubmit = false;
|
||
isInitializationComplete = false;
|
||
textureAcquiredOnce = false;
|
||
|
||
foreach (Texture externalTexture in layerTextures.externalTextures)
|
||
{
|
||
DEBUG("DestroyCompositionLayer: External textures");
|
||
if (externalTexture != null) Destroy(externalTexture);
|
||
}
|
||
layerTextures = null;
|
||
|
||
if (generatedFallbackMeshFilter != null && generatedFallbackMeshFilter.mesh != null)
|
||
{
|
||
DEBUG("DestroyCompositionLayer: generatedFallbackMeshFilter");
|
||
Destroy(generatedFallbackMeshFilter.mesh);
|
||
generatedFallbackMeshFilter = null;
|
||
}
|
||
if (generatedFallbackMeshRenderer != null && generatedFallbackMeshRenderer.material != null)
|
||
{
|
||
DEBUG("DestroyCompositionLayer: generatedFallbackMeshRenderer");
|
||
Destroy(generatedFallbackMeshRenderer.material);
|
||
generatedFallbackMeshRenderer = null;
|
||
}
|
||
|
||
if (generatedUnderlayMeshFilter != null && generatedUnderlayMeshFilter.mesh != null)
|
||
{
|
||
DEBUG("DestroyCompositionLayer: generatedUnderlayMeshFilter");
|
||
Destroy(generatedUnderlayMeshFilter.mesh);
|
||
generatedUnderlayMeshFilter = null;
|
||
}
|
||
if (generatedUnderlayMeshRenderer != null && generatedUnderlayMeshRenderer.material != null)
|
||
{
|
||
DEBUG("DestroyCompositionLayer: generatedUnderlayMeshRenderer");
|
||
Destroy(generatedUnderlayMeshRenderer.material);
|
||
generatedUnderlayMeshRenderer = null;
|
||
}
|
||
|
||
if (generatedFallbackMesh != null)
|
||
{
|
||
Destroy(generatedFallbackMesh);
|
||
generatedFallbackMesh = null;
|
||
}
|
||
|
||
if (generatedUnderlayMesh != null)
|
||
{
|
||
Destroy(generatedUnderlayMesh);
|
||
generatedUnderlayMesh = null;
|
||
}
|
||
|
||
OnDestroyCompositionLayerDelegate?.Invoke();
|
||
}
|
||
|
||
private List<XRInputSubsystem> inputSubsystems = new List<XRInputSubsystem>();
|
||
XrCompositionLayerQuad CompositionLayerParamsQuad = new XrCompositionLayerQuad();
|
||
XrExtent2Df quadSize = new XrExtent2Df();
|
||
private XrCompositionLayerQuad AssignCompositionLayerParamsQuad()
|
||
{
|
||
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||
|
||
CompositionLayerParamsQuad.type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_QUAD;
|
||
CompositionLayerParamsQuad.layerFlags = ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT | ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
||
|
||
switch (layerVisibility)
|
||
{
|
||
default:
|
||
case Visibility.Both:
|
||
CompositionLayerParamsQuad.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_BOTH;
|
||
break;
|
||
case Visibility.Left:
|
||
CompositionLayerParamsQuad.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_LEFT;
|
||
break;
|
||
case Visibility.Right:
|
||
CompositionLayerParamsQuad.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_RIGHT;
|
||
break;
|
||
}
|
||
|
||
CompositionLayerParamsQuad.subImage.imageRect = layerTextures.textureLayout;
|
||
CompositionLayerParamsQuad.subImage.imageArrayIndex = 0;
|
||
GetCompositionLayerPose(ref CompositionLayerParamsQuad.pose); //Update isHeadLock
|
||
|
||
if (isHeadLock)
|
||
{
|
||
CompositionLayerParamsQuad.space = compositionLayerFeature.HeadLockSpace;
|
||
}
|
||
else
|
||
{
|
||
XRInputSubsystem subsystem = null;
|
||
SubsystemManager.GetInstances(inputSubsystems);
|
||
if (inputSubsystems.Count > 0)
|
||
{
|
||
subsystem = inputSubsystems[0];
|
||
}
|
||
|
||
if (subsystem != null)
|
||
{
|
||
TrackingOriginModeFlags trackingOriginMode = subsystem.GetTrackingOriginMode();
|
||
|
||
switch (trackingOriginMode)
|
||
{
|
||
default:
|
||
case TrackingOriginModeFlags.Floor:
|
||
CompositionLayerParamsQuad.space = compositionLayerFeature.WorldLockSpaceOriginOnFloor;
|
||
break;
|
||
case TrackingOriginModeFlags.Device:
|
||
CompositionLayerParamsQuad.space = compositionLayerFeature.WorldLockSpaceOriginOnHead;
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
CompositionLayerParamsQuad.space = compositionLayerFeature.WorldLockSpaceOriginOnFloor;
|
||
}
|
||
}
|
||
|
||
quadSize.width = m_QuadWidth;
|
||
quadSize.height = m_QuadHeight;
|
||
CompositionLayerParamsQuad.size = quadSize;
|
||
|
||
return CompositionLayerParamsQuad;
|
||
}
|
||
|
||
XrCompositionLayerCylinderKHR CompositionLayerParamsCylinder = new XrCompositionLayerCylinderKHR();
|
||
private XrCompositionLayerCylinderKHR AssignCompositionLayerParamsCylinder()
|
||
{
|
||
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||
|
||
CompositionLayerParamsCylinder.type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_CYLINDER_KHR;
|
||
CompositionLayerParamsCylinder.layerFlags = ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT | ViveCompositionLayerHelper.XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT;
|
||
|
||
if (isHeadLock)
|
||
{
|
||
CompositionLayerParamsCylinder.space = compositionLayerFeature.HeadLockSpace;
|
||
}
|
||
else
|
||
{
|
||
XRInputSubsystem subsystem = null;
|
||
SubsystemManager.GetInstances(inputSubsystems);
|
||
if (inputSubsystems.Count > 0)
|
||
{
|
||
subsystem = inputSubsystems[0];
|
||
}
|
||
|
||
if (subsystem != null)
|
||
{
|
||
TrackingOriginModeFlags trackingOriginMode = subsystem.GetTrackingOriginMode();
|
||
|
||
switch (trackingOriginMode)
|
||
{
|
||
default:
|
||
case TrackingOriginModeFlags.Floor:
|
||
DEBUG("TrackingOriginModeFlags.Floor");
|
||
CompositionLayerParamsCylinder.space = compositionLayerFeature.WorldLockSpaceOriginOnFloor;
|
||
break;
|
||
case TrackingOriginModeFlags.Device:
|
||
DEBUG("TrackingOriginModeFlags.Device");
|
||
CompositionLayerParamsCylinder.space = compositionLayerFeature.WorldLockSpaceOriginOnHead;
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
CompositionLayerParamsCylinder.space = compositionLayerFeature.WorldLockSpaceOriginOnFloor;
|
||
}
|
||
}
|
||
|
||
switch (layerVisibility)
|
||
{
|
||
default:
|
||
case Visibility.Both:
|
||
CompositionLayerParamsCylinder.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_BOTH;
|
||
break;
|
||
case Visibility.Left:
|
||
CompositionLayerParamsCylinder.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_LEFT;
|
||
break;
|
||
case Visibility.Right:
|
||
CompositionLayerParamsCylinder.eyeVisibility = XrEyeVisibility.XR_EYE_VISIBILITY_RIGHT;
|
||
break;
|
||
}
|
||
|
||
CompositionLayerParamsCylinder.subImage.imageRect = layerTextures.textureLayout;
|
||
CompositionLayerParamsCylinder.subImage.imageArrayIndex = 0;
|
||
GetCompositionLayerPose(ref CompositionLayerParamsCylinder.pose);
|
||
CompositionLayerParamsCylinder.radius = m_CylinderRadius;
|
||
CompositionLayerParamsCylinder.centralAngle = Mathf.Deg2Rad * m_CylinderAngleOfArc;
|
||
CompositionLayerParamsCylinder.aspectRatio = m_CylinderArcLength / m_CylinderHeight;
|
||
|
||
return CompositionLayerParamsCylinder;
|
||
}
|
||
|
||
private void ActivatePlaceholder()
|
||
{
|
||
if (Debug.isDebugBuild && !placeholderGenerated)
|
||
{
|
||
if (CompositionLayerManager.GetInstance().MaxLayerCount() == 0)//Platform does not support multi-layer. Show placeholder instead if in development build
|
||
{
|
||
DEBUG("Generate Placeholder");
|
||
compositionLayerPlaceholderPrefabGO = Instantiate((GameObject)Resources.Load("Prefabs/CompositionLayerDebugPlaceholder", typeof(GameObject)));
|
||
compositionLayerPlaceholderPrefabGO.name = "CompositionLayerDebugPlaceholder";
|
||
compositionLayerPlaceholderPrefabGO.transform.SetParent(this.transform);
|
||
compositionLayerPlaceholderPrefabGO.transform.position = this.transform.position;
|
||
compositionLayerPlaceholderPrefabGO.transform.rotation = this.transform.rotation;
|
||
compositionLayerPlaceholderPrefabGO.transform.localScale = this.transform.localScale;
|
||
|
||
Text placeholderText = compositionLayerPlaceholderPrefabGO.transform.GetChild(0).Find("Text").GetComponent<Text>();
|
||
|
||
placeholderText.text = placeholderText.text.Replace("{REASON}", "Device does not support Multi-Layer");
|
||
|
||
placeholderGenerated = true;
|
||
}
|
||
else if (CompositionLayerManager.GetInstance().MaxLayerCount() <= CompositionLayerManager.GetInstance().CurrentLayerCount())//Do not draw layer as limit is reached. Show placeholder instead if in development build
|
||
{
|
||
DEBUG("Generate Placeholder");
|
||
compositionLayerPlaceholderPrefabGO = Instantiate((GameObject)Resources.Load("Prefabs/CompositionLayerDebugPlaceholder", typeof(GameObject)));
|
||
compositionLayerPlaceholderPrefabGO.name = "CompositionLayerDebugPlaceholder";
|
||
compositionLayerPlaceholderPrefabGO.transform.SetParent(this.transform);
|
||
compositionLayerPlaceholderPrefabGO.transform.position = this.transform.position;
|
||
compositionLayerPlaceholderPrefabGO.transform.rotation = this.transform.rotation;
|
||
compositionLayerPlaceholderPrefabGO.transform.localScale = this.transform.localScale;
|
||
|
||
Text placeholderText = compositionLayerPlaceholderPrefabGO.transform.GetChild(0).Find("Text").GetComponent<Text>();
|
||
|
||
placeholderText.text = placeholderText.text.Replace("{REASON}", "Max number of layers exceeded");
|
||
|
||
placeholderGenerated = true;
|
||
}
|
||
}
|
||
else if (placeholderGenerated && compositionLayerPlaceholderPrefabGO != null)
|
||
{
|
||
DEBUG("Placeholder already generated, activating.");
|
||
compositionLayerPlaceholderPrefabGO.SetActive(true);
|
||
}
|
||
}
|
||
|
||
public bool RenderAsLayer()
|
||
{
|
||
if (placeholderGenerated && compositionLayerPlaceholderPrefabGO != null)
|
||
{
|
||
compositionLayerPlaceholderPrefabGO.SetActive(false);
|
||
}
|
||
|
||
if (isAutoFallbackActive)
|
||
{
|
||
generatedFallbackMesh.SetActive(false);
|
||
isAutoFallbackActive = false;
|
||
}
|
||
|
||
isRenderPriorityChanged = false;
|
||
|
||
//if Underlay Mesh is present but needs to be reconstructed
|
||
if (layerType == LayerType.Underlay)
|
||
{
|
||
if (!UnderlayMeshIsValid()) //Underlay Mesh needs to be generated
|
||
{
|
||
UnderlayMeshGeneration();
|
||
}
|
||
else if (LayerDimensionsChanged()) //if Underlay Mesh is present but needs to be updated
|
||
{
|
||
UnderlayMeshUpdate();
|
||
}
|
||
else generatedUnderlayMesh.SetActive(true);
|
||
}
|
||
|
||
return CompositionLayerInit();
|
||
}
|
||
|
||
public void RenderInGame()
|
||
{
|
||
compositionLayerFeature = compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||
if (compositionLayerFeature.enableAutoFallback)
|
||
{
|
||
if (!isAutoFallbackActive)
|
||
{
|
||
//Activate auto fallback
|
||
//Activate auto fallback
|
||
if (!FallbackMeshIsValid())
|
||
{
|
||
AutoFallbackMeshGeneration();
|
||
}
|
||
else if (LayerDimensionsChanged())
|
||
{
|
||
AutoFallbackMeshUpdate();
|
||
}
|
||
generatedFallbackMesh.SetActive(true);
|
||
isAutoFallbackActive = true;
|
||
}
|
||
}
|
||
else //Use placeholder
|
||
{
|
||
ActivatePlaceholder();
|
||
}
|
||
|
||
if (layerType == LayerType.Underlay && generatedUnderlayMesh != null)
|
||
{
|
||
generatedUnderlayMesh.SetActive(false);
|
||
}
|
||
|
||
isRenderPriorityChanged = false;
|
||
}
|
||
|
||
public void TerminateLayer()
|
||
{
|
||
DEBUG("TerminateLayer: layerID: " + layerID);
|
||
DestroyCompositionLayer();
|
||
|
||
if (placeholderGenerated && compositionLayerPlaceholderPrefabGO != null)
|
||
{
|
||
compositionLayerPlaceholderPrefabGO.SetActive(false);
|
||
}
|
||
|
||
if (isAutoFallbackActive)
|
||
{
|
||
generatedFallbackMesh.SetActive(false);
|
||
isAutoFallbackActive = false;
|
||
}
|
||
}
|
||
|
||
public bool TextureParamsChanged()
|
||
{
|
||
if (previousTexture != texture)
|
||
{
|
||
previousTexture = texture;
|
||
return true;
|
||
}
|
||
|
||
if (previousIsDynamicLayer != isDynamicLayer)
|
||
{
|
||
previousIsDynamicLayer = isDynamicLayer;
|
||
return true;
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
public bool LayerDimensionsChanged()
|
||
{
|
||
bool isChanged = false;
|
||
|
||
if (layerShape == LayerShape.Cylinder)
|
||
{
|
||
if (previousAngleOfArc != m_CylinderAngleOfArc ||
|
||
previousCylinderArcLength != m_CylinderArcLength ||
|
||
previousCylinderHeight != m_CylinderHeight ||
|
||
previousCylinderRadius != m_CylinderRadius)
|
||
{
|
||
previousAngleOfArc = m_CylinderAngleOfArc;
|
||
previousCylinderArcLength = m_CylinderArcLength;
|
||
previousCylinderHeight = m_CylinderHeight;
|
||
previousCylinderRadius = m_CylinderRadius;
|
||
isChanged = true;
|
||
}
|
||
}
|
||
else if (layerShape == LayerShape.Quad)
|
||
{
|
||
if (previousQuadWidth != m_QuadWidth ||
|
||
previousQuadHeight != m_QuadHeight)
|
||
{
|
||
previousQuadWidth = m_QuadWidth;
|
||
previousQuadHeight = m_QuadHeight;
|
||
isChanged = true;
|
||
}
|
||
}
|
||
|
||
if (previousLayerShape != layerShape)
|
||
{
|
||
previousLayerShape = layerShape;
|
||
isChanged = true;
|
||
}
|
||
|
||
return isChanged;
|
||
}
|
||
|
||
#region Quad Runtime Parameter Change
|
||
/// <summary>
|
||
/// Use this function to update the width of a Quad Layer.
|
||
/// </summary>
|
||
/// <param name="inWidth"></param>
|
||
/// <returns></returns>
|
||
public bool SetQuadLayerWidth(float inWidth)
|
||
{
|
||
if (inWidth <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
m_QuadWidth = inWidth;
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Use this function to update the height of a Quad Layer.
|
||
/// </summary>
|
||
/// <param name="inHeight"></param>
|
||
/// <returns></returns>
|
||
public bool SetQuadLayerHeight(float inHeight)
|
||
{
|
||
if (inHeight <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
m_QuadHeight = inHeight;
|
||
|
||
return true;
|
||
}
|
||
#endregion
|
||
|
||
#region Cylinder Runtime Parameter Change
|
||
/// <summary>
|
||
/// Use this function to update the radius and arc angle of a Cylinder Layer.
|
||
/// A new arc length will be calculated automatically.
|
||
/// </summary>
|
||
/// <param name="inRadius"></param>
|
||
/// <param name="inArcAngle"></param>
|
||
/// <returns>True if the parameters are valid and successfully updated.</returns>
|
||
public bool SetCylinderLayerRadiusAndArcAngle(float inRadius, float inArcAngle)
|
||
{
|
||
//Check if radius is valid
|
||
if (inRadius <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//Check if angle of arc is valid
|
||
if (inArcAngle < angleOfArcLowerLimit || inArcAngle > angleOfArcUpperLimit)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//Check if new arc length is valid
|
||
float newArcLength = CylinderParameterHelper.RadiusAndDegAngleOfArcToArcLength(inArcAngle, inRadius);
|
||
if (newArcLength <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//All parameters are valid, assign to layer
|
||
m_CylinderArcLength = newArcLength;
|
||
m_CylinderRadius = inRadius;
|
||
m_CylinderAngleOfArc = inArcAngle;
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Use this function to update the radius and arc length of a Cylinder Layer.
|
||
/// A new arc angle will be calculated automatically.
|
||
/// </summary>
|
||
/// <param name="inRadius"></param>
|
||
/// <param name="inArcLength"></param>
|
||
/// <returns>True if the parameters are valid and successfully updated.</returns>
|
||
public bool SetCylinderLayerRadiusAndArcLength(float inRadius, float inArcLength)
|
||
{
|
||
//Check if radius is valid
|
||
if (inRadius <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//Check if arc length is valid
|
||
if (inArcLength <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//Check if new arc angle is valid
|
||
float newArcAngle = CylinderParameterHelper.RadiusAndArcLengthToDegAngleOfArc(inArcLength, inRadius);
|
||
if (newArcAngle < angleOfArcLowerLimit || newArcAngle > angleOfArcUpperLimit)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//All parameters are valid, assign to layer
|
||
m_CylinderArcLength = inArcLength;
|
||
m_CylinderRadius = inRadius;
|
||
m_CylinderAngleOfArc = newArcAngle;
|
||
|
||
return true;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Use this function to update the arc angle and arc length of a Cylinder Layer.
|
||
/// A new radius will be calculated automatically.
|
||
/// </summary>
|
||
/// <param name="inArcAngle"></param>
|
||
/// <param name="inArcLength"></param>
|
||
/// <returns>True if the parameters are valid and successfully updated.</returns>
|
||
public bool SetCylinderLayerArcAngleAndArcLength(float inArcAngle, float inArcLength)
|
||
{
|
||
//Check if arc angle is valid
|
||
if (inArcAngle < angleOfArcLowerLimit || inArcAngle > angleOfArcUpperLimit)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//Check if arc length is valid
|
||
if (inArcLength <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//Check if new radius is valid
|
||
float newRadius = CylinderParameterHelper.ArcLengthAndDegAngleOfArcToRadius(inArcLength, inArcAngle);
|
||
if (newRadius <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
//All parameters are valid, assign to layer
|
||
m_CylinderArcLength = inArcLength;
|
||
m_CylinderRadius = newRadius;
|
||
m_CylinderAngleOfArc = inArcAngle;
|
||
|
||
return true;
|
||
|
||
}
|
||
|
||
/// <summary>
|
||
/// Use this function to update the height of a Cylinder Layer.
|
||
/// </summary>
|
||
/// <param name="inHeight"></param>
|
||
/// <returns></returns>
|
||
public bool SetCylinderLayerHeight(float inHeight)
|
||
{
|
||
if (inHeight <= 0)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
m_CylinderHeight = inHeight;
|
||
|
||
return true;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#if UNITY_EDITOR
|
||
public CylinderLayerParamAdjustmentMode CurrentAdjustmentMode()
|
||
{
|
||
if (previousCylinderArcLength != m_CylinderArcLength)
|
||
{
|
||
return CylinderLayerParamAdjustmentMode.ArcLength;
|
||
}
|
||
else if (previousAngleOfArc != m_CylinderAngleOfArc)
|
||
{
|
||
return CylinderLayerParamAdjustmentMode.ArcAngle;
|
||
}
|
||
else
|
||
{
|
||
return CylinderLayerParamAdjustmentMode.Radius;
|
||
}
|
||
}
|
||
#endif
|
||
|
||
public void ChangeBlitShadermode(BlitShaderMode shaderMode, bool enable)
|
||
{
|
||
if (texture2DBlitMaterial == null) return;
|
||
|
||
switch (shaderMode)
|
||
{
|
||
case BlitShaderMode.LINEAR_TO_SRGB_COLOR:
|
||
if (enable)
|
||
{
|
||
texture2DBlitMaterial.EnableKeyword("LINEAR_TO_SRGB_COLOR");
|
||
}
|
||
else
|
||
{
|
||
texture2DBlitMaterial.DisableKeyword("LINEAR_TO_SRGB_COLOR");
|
||
}
|
||
break;
|
||
case BlitShaderMode.LINEAR_TO_SRGB_ALPHA:
|
||
if (enable)
|
||
{
|
||
texture2DBlitMaterial.EnableKeyword("LINEAR_TO_SRGB_ALPHA");
|
||
}
|
||
else
|
||
{
|
||
texture2DBlitMaterial.DisableKeyword("LINEAR_TO_SRGB_ALPHA");
|
||
}
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#region Monobehavior Lifecycle
|
||
private void Awake()
|
||
{
|
||
//Create blit mat
|
||
texture2DBlitMaterial = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/Texture2DBlitShader"));
|
||
|
||
//Create render thread synchornizer
|
||
if (synchronizer == null) synchronizer = new RenderThreadSynchronizer();
|
||
|
||
ColorSpace currentActiveColorSpace = QualitySettings.activeColorSpace;
|
||
if (currentActiveColorSpace == ColorSpace.Linear)
|
||
{
|
||
isLinear = true;
|
||
}
|
||
else
|
||
{
|
||
isLinear = false;
|
||
}
|
||
|
||
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
hmd = Camera.main;
|
||
|
||
CompositionLayerManager.GetInstance().SubscribeToLayerManager(this);
|
||
if (!isOnBeforeRenderSubscribed)
|
||
{
|
||
CompositionLayerManager.GetInstance().CompositionLayerManagerOnBeforeRenderDelegate += OnBeforeRender;
|
||
isOnBeforeRenderSubscribed = true;
|
||
}
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
if (isOnBeforeRenderSubscribed)
|
||
{
|
||
CompositionLayerManager.GetInstance().CompositionLayerManagerOnBeforeRenderDelegate -= OnBeforeRender;
|
||
isOnBeforeRenderSubscribed = false;
|
||
}
|
||
CompositionLayerManager.GetInstance().UnsubscribeFromLayerManager(this, false);
|
||
}
|
||
|
||
private void OnDestroy()
|
||
{
|
||
if (isOnBeforeRenderSubscribed)
|
||
{
|
||
CompositionLayerManager.GetInstance().CompositionLayerManagerOnBeforeRenderDelegate -= OnBeforeRender;
|
||
isOnBeforeRenderSubscribed = false;
|
||
}
|
||
|
||
Destroy(texture2DBlitMaterial);
|
||
|
||
CompositionLayerManager.GetInstance().UnsubscribeFromLayerManager(this, true);
|
||
}
|
||
|
||
private void LateUpdate()
|
||
{
|
||
if (isAutoFallbackActive) //Do not submit when auto fallback is active
|
||
{
|
||
//Check if auto fallback mesh needs to be updated
|
||
if (!FallbackMeshIsValid()) //fallback Mesh needs to be generated
|
||
{
|
||
AutoFallbackMeshGeneration();
|
||
}
|
||
else if (LayerDimensionsChanged()) //if fallback Mesh is present but needs to be updated
|
||
{
|
||
AutoFallbackMeshUpdate();
|
||
}
|
||
|
||
//Handle possible lossy scale change
|
||
if (generatedFallbackMesh.transform.lossyScale != Vector3.one)
|
||
{
|
||
generatedFallbackMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
|
||
}
|
||
|
||
return;
|
||
}
|
||
|
||
if (layerType == LayerType.Underlay)
|
||
{
|
||
if (!UnderlayMeshIsValid()) //Underlay Mesh needs to be generated
|
||
{
|
||
UnderlayMeshGeneration();
|
||
}
|
||
else if (LayerDimensionsChanged()) //if Underlay Mesh is present but needs to be updated
|
||
{
|
||
UnderlayMeshUpdate();
|
||
}
|
||
|
||
//Handle possible lossy scale change
|
||
if (generatedUnderlayMesh.transform.lossyScale != Vector3.one)
|
||
{
|
||
generatedUnderlayMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
|
||
}
|
||
|
||
generatedUnderlayMesh.SetActive(true);
|
||
}
|
||
}
|
||
|
||
private void OnBeforeRender()
|
||
{
|
||
compositionLayerFeature = OpenXRSettings.Instance.GetFeature<ViveCompositionLayer>();
|
||
|
||
isLayerReadyForSubmit = false;
|
||
|
||
if (compositionLayerFeature.XrSessionEnding) return;
|
||
|
||
if (!isInitializationComplete) //Layer not marked as initialized
|
||
{
|
||
if (InitStatus) //Initialized
|
||
{
|
||
reinitializationNeeded = false;
|
||
isInitializationComplete = true;
|
||
isSynchronized = false;
|
||
}
|
||
else if (reinitializationNeeded) //Layer is still active but needs to be reinitialized
|
||
{
|
||
CompositionLayerInit();
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
DEBUG("Composition Layer Lifecycle OnBeforeRender: Layer not initialized.");
|
||
return;
|
||
}
|
||
}
|
||
|
||
if (!isSynchronized)
|
||
{
|
||
DEBUG("CompositionLayer: Sync");
|
||
if (synchronizer != null)
|
||
{
|
||
synchronizer.sync();
|
||
isSynchronized = true;
|
||
}
|
||
}
|
||
|
||
if (isAutoFallbackActive || ((compositionLayerPlaceholderPrefabGO != null) && compositionLayerPlaceholderPrefabGO.activeSelf)) //Do not submit when auto fallback or placeholder is active
|
||
{
|
||
return;
|
||
}
|
||
|
||
if (compositionLayerFeature.XrSessionCurrentState != XrSessionState.XR_SESSION_STATE_VISIBLE && compositionLayerFeature.XrSessionCurrentState != XrSessionState.XR_SESSION_STATE_FOCUSED)
|
||
{
|
||
//DEBUG("XrSessionCurrentState is not focused or visible");
|
||
return;
|
||
}
|
||
|
||
if (SetLayerTexture())
|
||
{
|
||
isLayerReadyForSubmit = true;
|
||
}
|
||
|
||
if (!isLayerReadyForSubmit)
|
||
{
|
||
DEBUG("Composition Layer Lifecycle OnBeforeRender: Layer not ready for submit.");
|
||
return;
|
||
}
|
||
SubmitCompositionLayer();
|
||
|
||
isLayerReadyForSubmit = false; //reset flag after submit
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Mesh Generation
|
||
public void AutoFallbackMeshGeneration()
|
||
{
|
||
if (generatedFallbackMeshFilter != null && generatedFallbackMeshFilter.mesh != null)
|
||
{
|
||
Destroy(generatedFallbackMeshFilter.mesh);
|
||
}
|
||
if (generatedFallbackMeshRenderer != null && generatedFallbackMeshRenderer.material != null)
|
||
{
|
||
Destroy(generatedFallbackMeshRenderer.material);
|
||
}
|
||
if (generatedFallbackMesh != null) Destroy(generatedFallbackMesh);
|
||
|
||
Mesh generatedMesh = null;
|
||
|
||
switch (layerShape)
|
||
{
|
||
case LayerShape.Quad:
|
||
generatedMesh = MeshGenerationHelper.GenerateQuadMesh(MeshGenerationHelper.GenerateQuadVertex(m_QuadWidth, m_QuadHeight));
|
||
break;
|
||
case LayerShape.Cylinder:
|
||
generatedMesh = MeshGenerationHelper.GenerateCylinderMesh(m_CylinderAngleOfArc, MeshGenerationHelper.GenerateCylinderVertex(m_CylinderAngleOfArc, m_CylinderRadius, m_CylinderHeight));
|
||
break;
|
||
}
|
||
|
||
generatedFallbackMesh = new GameObject();
|
||
generatedFallbackMesh.SetActive(false);
|
||
|
||
generatedFallbackMesh.name = FallbackMeshName;
|
||
generatedFallbackMesh.transform.SetParent(gameObject.transform);
|
||
generatedFallbackMesh.transform.localPosition = Vector3.zero;
|
||
generatedFallbackMesh.transform.localRotation = Quaternion.identity;
|
||
|
||
generatedFallbackMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
|
||
|
||
generatedFallbackMeshRenderer = generatedFallbackMesh.AddComponent<MeshRenderer>();
|
||
generatedFallbackMeshFilter = generatedFallbackMesh.AddComponent<MeshFilter>();
|
||
|
||
generatedFallbackMeshFilter.mesh = generatedMesh;
|
||
|
||
Material fallBackMat = new Material(Shader.Find("Unlit/Transparent"));
|
||
fallBackMat.mainTexture = texture;
|
||
generatedFallbackMeshRenderer.material = fallBackMat;
|
||
}
|
||
|
||
public void AutoFallbackMeshUpdate()
|
||
{
|
||
if (generatedFallbackMesh == null || generatedFallbackMeshRenderer == null || generatedFallbackMeshFilter == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
Mesh generatedMesh = null;
|
||
|
||
switch (layerShape)
|
||
{
|
||
case LayerShape.Quad:
|
||
generatedMesh = MeshGenerationHelper.GenerateQuadMesh(MeshGenerationHelper.GenerateQuadVertex(m_QuadWidth, m_QuadHeight));
|
||
break;
|
||
case LayerShape.Cylinder:
|
||
generatedMesh = MeshGenerationHelper.GenerateCylinderMesh(m_CylinderAngleOfArc, MeshGenerationHelper.GenerateCylinderVertex(m_CylinderAngleOfArc, m_CylinderRadius, m_CylinderHeight));
|
||
break;
|
||
}
|
||
|
||
generatedFallbackMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
|
||
Destroy(generatedFallbackMeshFilter.mesh);
|
||
generatedFallbackMeshFilter.mesh = generatedMesh;
|
||
generatedFallbackMeshRenderer.material.mainTexture = texture;
|
||
}
|
||
|
||
public bool FallbackMeshIsValid()
|
||
{
|
||
if (generatedFallbackMesh == null || generatedFallbackMeshRenderer == null || generatedFallbackMeshFilter == null)
|
||
{
|
||
return false;
|
||
}
|
||
else if (generatedFallbackMeshFilter.mesh == null || generatedFallbackMeshRenderer.material == null)
|
||
{
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
public void UnderlayMeshGeneration()
|
||
{
|
||
if (generatedUnderlayMeshFilter != null && generatedUnderlayMeshFilter.mesh != null)
|
||
{
|
||
Destroy(generatedUnderlayMeshFilter.mesh);
|
||
}
|
||
if (generatedUnderlayMeshRenderer != null && generatedUnderlayMeshRenderer.material != null)
|
||
{
|
||
Destroy(generatedUnderlayMeshRenderer.material);
|
||
}
|
||
if (generatedUnderlayMesh != null) Destroy(generatedUnderlayMesh);
|
||
|
||
switch (layerShape)
|
||
{
|
||
case LayerShape.Cylinder:
|
||
//Generate vertices
|
||
Vector3[] cylinderVertices = MeshGenerationHelper.GenerateCylinderVertex(m_CylinderAngleOfArc, m_CylinderRadius, m_CylinderHeight);
|
||
|
||
//Add components to Game Object
|
||
generatedUnderlayMesh = new GameObject();
|
||
generatedUnderlayMesh.name = CylinderUnderlayMeshName;
|
||
generatedUnderlayMesh.transform.SetParent(transform);
|
||
generatedUnderlayMesh.transform.localPosition = Vector3.zero;
|
||
generatedUnderlayMesh.transform.localRotation = Quaternion.identity;
|
||
|
||
generatedUnderlayMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
|
||
|
||
generatedUnderlayMeshRenderer = generatedUnderlayMesh.AddComponent<MeshRenderer>();
|
||
generatedUnderlayMeshFilter = generatedUnderlayMesh.AddComponent<MeshFilter>();
|
||
generatedUnderlayMeshRenderer.sharedMaterial = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/UnderlayAlphaZero"));
|
||
generatedUnderlayMeshRenderer.material.mainTexture = texture;
|
||
|
||
//Generate Mesh
|
||
generatedUnderlayMeshFilter.mesh = MeshGenerationHelper.GenerateCylinderMesh(m_CylinderAngleOfArc, cylinderVertices);
|
||
break;
|
||
case LayerShape.Quad:
|
||
default:
|
||
//Generate vertices
|
||
Vector3[] quadVertices = MeshGenerationHelper.GenerateQuadVertex(m_QuadWidth, m_QuadHeight);
|
||
|
||
//Add components to Game Object
|
||
generatedUnderlayMesh = new GameObject();
|
||
generatedUnderlayMesh.name = QuadUnderlayMeshName;
|
||
generatedUnderlayMesh.transform.SetParent(transform);
|
||
generatedUnderlayMesh.transform.localPosition = Vector3.zero;
|
||
generatedUnderlayMesh.transform.localRotation = Quaternion.identity;
|
||
|
||
generatedUnderlayMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
|
||
|
||
generatedUnderlayMeshRenderer = generatedUnderlayMesh.AddComponent<MeshRenderer>();
|
||
generatedUnderlayMeshFilter = generatedUnderlayMesh.AddComponent<MeshFilter>();
|
||
generatedUnderlayMeshRenderer.material = new Material(Shader.Find("VIVE/OpenXR/CompositionLayer/UnderlayAlphaZero"));
|
||
generatedUnderlayMeshRenderer.material.mainTexture = texture;
|
||
|
||
//Generate Mesh
|
||
generatedUnderlayMeshFilter.mesh = MeshGenerationHelper.GenerateQuadMesh(quadVertices);
|
||
break;
|
||
}
|
||
}
|
||
|
||
public void UnderlayMeshUpdate()
|
||
{
|
||
if (generatedUnderlayMesh == null || generatedUnderlayMeshRenderer == null || generatedUnderlayMeshFilter == null)
|
||
{
|
||
return;
|
||
}
|
||
|
||
switch (layerShape)
|
||
{
|
||
case LayerShape.Cylinder:
|
||
//Generate vertices
|
||
Vector3[] cylinderVertices = MeshGenerationHelper.GenerateCylinderVertex(m_CylinderAngleOfArc, m_CylinderRadius, m_CylinderHeight);
|
||
|
||
//Generate Mesh
|
||
Destroy(generatedUnderlayMeshFilter.mesh);
|
||
generatedUnderlayMeshFilter.mesh = MeshGenerationHelper.GenerateCylinderMesh(m_CylinderAngleOfArc, cylinderVertices);
|
||
break;
|
||
case LayerShape.Quad:
|
||
default:
|
||
//Generate vertices
|
||
Vector3[] quadVertices = MeshGenerationHelper.GenerateQuadVertex(m_QuadWidth, m_QuadHeight);
|
||
|
||
//Generate Mesh
|
||
Destroy(generatedUnderlayMeshFilter.mesh);
|
||
generatedUnderlayMeshFilter.mesh = MeshGenerationHelper.GenerateQuadMesh(quadVertices);
|
||
break;
|
||
}
|
||
|
||
generatedUnderlayMesh.transform.localScale = GetNormalizedLocalScale(transform, Vector3.one);
|
||
}
|
||
|
||
public Vector3 GetNormalizedLocalScale(Transform targetTransform, Vector3 targetGlobalScale) //Return the local scale needed to make it match to the target global scale
|
||
{
|
||
Vector3 normalizedLocalScale = new Vector3(targetGlobalScale.x / targetTransform.lossyScale.x, targetGlobalScale.y / targetTransform.lossyScale.y, targetGlobalScale.z / targetTransform.lossyScale.z);
|
||
|
||
return normalizedLocalScale;
|
||
}
|
||
|
||
public bool UnderlayMeshIsValid()
|
||
{
|
||
if (generatedUnderlayMesh == null || generatedUnderlayMeshRenderer == null || generatedUnderlayMeshFilter == null)
|
||
{
|
||
return false;
|
||
}
|
||
else if (generatedUnderlayMeshFilter.mesh == null || generatedUnderlayMeshRenderer.material == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Enum Definitions
|
||
public enum LayerType
|
||
{
|
||
Overlay = 1,
|
||
Underlay = 2,
|
||
}
|
||
|
||
public enum LayerShape
|
||
{
|
||
Quad = 0,
|
||
Cylinder = 1,
|
||
}
|
||
|
||
public enum Visibility
|
||
{
|
||
Both = 0,
|
||
Left = 1,
|
||
Right = 2,
|
||
}
|
||
|
||
#if UNITY_EDITOR
|
||
public enum CylinderLayerParamAdjustmentMode
|
||
{
|
||
Radius = 0,
|
||
ArcLength = 1,
|
||
ArcAngle = 2,
|
||
}
|
||
|
||
public enum CylinderLayerParamLockMode
|
||
{
|
||
ArcLength = 0,
|
||
ArcAngle = 1,
|
||
}
|
||
#endif
|
||
|
||
public enum BlitShaderMode
|
||
{
|
||
LINEAR_TO_SRGB_COLOR = 0,
|
||
LINEAR_TO_SRGB_ALPHA = 1,
|
||
}
|
||
#endregion
|
||
|
||
#region Helper Classes
|
||
private class LayerTextures
|
||
{
|
||
private uint layerTextureQueueLength;
|
||
|
||
public uint currentAvailableTextureIndex { get; set; }
|
||
public IntPtr[] textureIDs;
|
||
public Texture[] externalTextures;
|
||
public bool[] textureContentSet;
|
||
public XrRect2Di textureLayout { get; set; }
|
||
|
||
public LayerTextures(uint swapchainImageCount)
|
||
{
|
||
layerTextureQueueLength = swapchainImageCount;
|
||
textureIDs = new IntPtr[swapchainImageCount];
|
||
externalTextures = new Texture[swapchainImageCount];
|
||
textureContentSet = new bool[swapchainImageCount];
|
||
|
||
for (int i = 0; i < swapchainImageCount; i++)
|
||
{
|
||
textureContentSet[i] = false;
|
||
textureIDs[i] = IntPtr.Zero;
|
||
}
|
||
}
|
||
|
||
public IntPtr GetCurrentAvailableTextureID()
|
||
{
|
||
if (currentAvailableTextureIndex < 0 || currentAvailableTextureIndex > layerTextureQueueLength - 1)
|
||
{
|
||
return IntPtr.Zero;
|
||
}
|
||
return textureIDs[currentAvailableTextureIndex];
|
||
}
|
||
|
||
public void SetCurrentAvailableTextureID(IntPtr newTextureID)
|
||
{
|
||
if (currentAvailableTextureIndex < 0 || currentAvailableTextureIndex > layerTextureQueueLength - 1)
|
||
{
|
||
return;
|
||
}
|
||
textureIDs[currentAvailableTextureIndex] = newTextureID;
|
||
}
|
||
|
||
public Texture GetCurrentAvailableExternalTexture()
|
||
{
|
||
if (currentAvailableTextureIndex < 0 || currentAvailableTextureIndex > layerTextureQueueLength - 1)
|
||
{
|
||
return null;
|
||
}
|
||
return externalTextures[currentAvailableTextureIndex];
|
||
}
|
||
|
||
public void SetCurrentAvailableExternalTexture(Texture newExternalTexture)
|
||
{
|
||
if (currentAvailableTextureIndex < 0 || currentAvailableTextureIndex > layerTextureQueueLength - 1)
|
||
{
|
||
return;
|
||
}
|
||
externalTextures[currentAvailableTextureIndex] = newExternalTexture;
|
||
}
|
||
}
|
||
|
||
private class CompositionLayerRenderThreadTask : Task
|
||
{
|
||
public int layerID;
|
||
|
||
public CompositionLayerRenderThreadTask() { }
|
||
|
||
public static void IssueObtainSwapchainEvent(CompositionLayerRenderThreadSyncObject syncObject)
|
||
{
|
||
PreAllocatedQueue taskQueue = syncObject.Queue;
|
||
lock (taskQueue)
|
||
{
|
||
CompositionLayerRenderThreadTask task = taskQueue.Obtain<CompositionLayerRenderThreadTask>();
|
||
taskQueue.Enqueue(task);
|
||
}
|
||
syncObject.IssueEvent();
|
||
}
|
||
|
||
public static void IssueDestroySwapchainEvent(CompositionLayerRenderThreadSyncObject syncObject, int inLayerID)
|
||
{
|
||
PreAllocatedQueue taskQueue = syncObject.Queue;
|
||
lock (taskQueue)
|
||
{
|
||
CompositionLayerRenderThreadTask task = taskQueue.Obtain<CompositionLayerRenderThreadTask>();
|
||
task.layerID = inLayerID;
|
||
taskQueue.Enqueue(task);
|
||
}
|
||
syncObject.IssueEvent();
|
||
}
|
||
}
|
||
|
||
private class RenderThreadSynchronizer
|
||
{
|
||
RenderTexture mutable = new RenderTexture(1, 1, 0);
|
||
public RenderThreadSynchronizer()
|
||
{
|
||
mutable.useMipMap = false;
|
||
mutable.Create();
|
||
}
|
||
|
||
public void sync()
|
||
{
|
||
var originalLogType = Application.GetStackTraceLogType(LogType.Error);
|
||
Application.SetStackTraceLogType(LogType.Error, StackTraceLogType.None);
|
||
|
||
// Sync
|
||
mutable.GetNativeTexturePtr();
|
||
|
||
Application.SetStackTraceLogType(LogType.Error, originalLogType);
|
||
}
|
||
}
|
||
|
||
private class UnityToOpenXRConversionHelper
|
||
{
|
||
public static void GetOpenXRQuaternion(Quaternion rot, ref XrQuaternionf qua)
|
||
{
|
||
qua.x = rot.x;
|
||
qua.y = rot.y;
|
||
qua.z = -rot.z;
|
||
qua.w = -rot.w;
|
||
}
|
||
|
||
public static void GetOpenXRVector(Vector3 pos, ref XrVector3f vec)
|
||
{
|
||
vec.x = pos.x;
|
||
vec.y = pos.y;
|
||
vec.z = -pos.z;
|
||
}
|
||
|
||
public static void GetOpenXRColor4f(Color color, ref XrColor4f color4f)
|
||
{
|
||
color4f.r = color.r;
|
||
color4f.g = color.g;
|
||
color4f.b = color.b;
|
||
color4f.a = color.a;
|
||
}
|
||
}
|
||
|
||
public static class MeshGenerationHelper
|
||
{
|
||
public static Vector3[] GenerateQuadVertex(float quadWidth, float quadHeight)
|
||
{
|
||
Vector3[] vertices = new Vector3[4]; //Four corners
|
||
|
||
vertices[0] = new Vector3(-quadWidth / 2, -quadHeight / 2, 0); //Bottom Left
|
||
vertices[1] = new Vector3(quadWidth / 2, -quadHeight / 2, 0); //Bottom Right
|
||
vertices[2] = new Vector3(-quadWidth / 2, quadHeight / 2, 0); //Top Left
|
||
vertices[3] = new Vector3(quadWidth / 2, quadHeight / 2, 0); //Top Right
|
||
|
||
return vertices;
|
||
}
|
||
|
||
public static Mesh GenerateQuadMesh(Vector3[] vertices)
|
||
{
|
||
Mesh quadMesh = new Mesh();
|
||
quadMesh.vertices = vertices;
|
||
|
||
//Create array that represents vertices of the triangles
|
||
int[] triangles = new int[6];
|
||
triangles[0] = 0;
|
||
triangles[1] = 2;
|
||
triangles[2] = 1;
|
||
|
||
triangles[3] = 1;
|
||
triangles[4] = 2;
|
||
triangles[5] = 3;
|
||
|
||
quadMesh.triangles = triangles;
|
||
Vector2[] uv = new Vector2[vertices.Length];
|
||
Vector4[] tangents = new Vector4[vertices.Length];
|
||
Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
|
||
for (int i = 0, y = 0; y < 2; y++)
|
||
{
|
||
for (int x = 0; x < 2; x++, i++)
|
||
{
|
||
uv[i] = new Vector2((float)x, (float)y);
|
||
tangents[i] = tangent;
|
||
}
|
||
}
|
||
quadMesh.uv = uv;
|
||
quadMesh.tangents = tangents;
|
||
quadMesh.RecalculateNormals();
|
||
|
||
return quadMesh;
|
||
}
|
||
|
||
public static Vector3[] GenerateCylinderVertex(float cylinderAngleOfArc, float cylinderRadius, float cylinderHeight)
|
||
{
|
||
float angleUpperLimitDeg = cylinderAngleOfArc / 2; //Degrees
|
||
float angleLowerLimitDeg = -angleUpperLimitDeg; //Degrees
|
||
|
||
float angleUpperLimitRad = angleUpperLimitDeg * Mathf.Deg2Rad; //Radians
|
||
float angleLowerLimitRad = angleLowerLimitDeg * Mathf.Deg2Rad; //Radians
|
||
|
||
int arcSegments = Mathf.RoundToInt(cylinderAngleOfArc / 5f);
|
||
|
||
float anglePerArcSegmentRad = (cylinderAngleOfArc / arcSegments) * Mathf.Deg2Rad;
|
||
|
||
Vector3[] vertices = new Vector3[2 * (arcSegments + 1)]; //Top and bottom lines * Vertex count per line
|
||
|
||
int vertexCount = 0;
|
||
for (int i = 0; i < 2; i++)
|
||
{
|
||
for (int j = 0; j < arcSegments + 1; j++) //Clockwise
|
||
{
|
||
float currentVertexAngleRad = angleLowerLimitRad + anglePerArcSegmentRad * j;
|
||
float x = cylinderRadius * Mathf.Sin(currentVertexAngleRad);
|
||
float y = 0;
|
||
float z = cylinderRadius * Mathf.Cos(currentVertexAngleRad);
|
||
|
||
if (i == 1) //Top
|
||
{
|
||
y += cylinderHeight / 2;
|
||
|
||
}
|
||
else //Bottom
|
||
{
|
||
y -= cylinderHeight / 2;
|
||
}
|
||
|
||
vertices[vertexCount] = new Vector3(x, y, z);
|
||
vertexCount++;
|
||
}
|
||
}
|
||
|
||
return vertices;
|
||
}
|
||
|
||
public static Mesh GenerateCylinderMesh(float cylinderAngleOfArc, Vector3[] vertices)
|
||
{
|
||
Mesh cylinderMesh = new Mesh();
|
||
cylinderMesh.vertices = vertices;
|
||
int arcSegments = Mathf.RoundToInt(cylinderAngleOfArc / 5f);
|
||
|
||
//Create array that represents vertices of the triangles
|
||
int[] triangles = new int[arcSegments * 6];
|
||
for (int currentTriangleIndex = 0, currentVertexIndex = 0, y = 0; y < 1; y++, currentVertexIndex++)
|
||
{
|
||
for (int x = 0; x < arcSegments; x++, currentTriangleIndex += 6, currentVertexIndex++)
|
||
{
|
||
triangles[currentTriangleIndex] = currentVertexIndex;
|
||
triangles[currentTriangleIndex + 1] = currentVertexIndex + arcSegments + 1;
|
||
triangles[currentTriangleIndex + 2] = currentVertexIndex + 1;
|
||
|
||
triangles[currentTriangleIndex + 3] = currentVertexIndex + 1;
|
||
triangles[currentTriangleIndex + 4] = currentVertexIndex + arcSegments + 1;
|
||
triangles[currentTriangleIndex + 5] = currentVertexIndex + arcSegments + 2;
|
||
}
|
||
}
|
||
cylinderMesh.triangles = triangles;
|
||
Vector2[] uv = new Vector2[vertices.Length];
|
||
Vector4[] tangents = new Vector4[vertices.Length];
|
||
Vector4 tangent = new Vector4(1f, 0f, 0f, -1f);
|
||
for (int i = 0, y = 0; y < 2; y++)
|
||
{
|
||
for (int x = 0; x < arcSegments + 1; x++, i++)
|
||
{
|
||
uv[i] = new Vector2((float)x / arcSegments, (float)y);
|
||
tangents[i] = tangent;
|
||
}
|
||
}
|
||
cylinderMesh.uv = uv;
|
||
cylinderMesh.tangents = tangents;
|
||
cylinderMesh.RecalculateNormals();
|
||
|
||
return cylinderMesh;
|
||
}
|
||
}
|
||
|
||
public static class CylinderParameterHelper
|
||
{
|
||
public static float RadiusAndDegAngleOfArcToArcLength(float inDegAngleOfArc, float inRadius)
|
||
{
|
||
float arcLength = inRadius * (inDegAngleOfArc * Mathf.Deg2Rad);
|
||
|
||
return arcLength;
|
||
}
|
||
|
||
public static float RadiusAndArcLengthToDegAngleOfArc(float inArcLength, float inRadius)
|
||
{
|
||
float degAngleOfArc = (inArcLength / inRadius) * Mathf.Rad2Deg;
|
||
|
||
return degAngleOfArc;
|
||
}
|
||
|
||
public static float ArcLengthAndDegAngleOfArcToRadius(float inArcLength, float inDegAngleOfArc)
|
||
{
|
||
float radius = (inArcLength / (inDegAngleOfArc * Mathf.Deg2Rad));
|
||
|
||
return radius;
|
||
}
|
||
}
|
||
#endregion
|
||
}
|
||
}
|