1845 lines
56 KiB
C#
1845 lines
56 KiB
C#
// Copyright HTC Corporation All Rights Reserved.
|
|
|
|
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
|
|
}
|
|
}
|