/************************************************************************************ Depthkit Unity SDK License v1 Copyright 2016-2024 Simile Inc dba Scatter. All Rights reserved. Licensed under the the Simile Inc dba Scatter ("Scatter") Software Development Kit License Agreement (the "License"); you may not use this SDK except in compliance with the License, which is provided at the time of installation or download, or which otherwise accompanies this software in either electronic or hard copy form. You may obtain a copy of the License at http://www.depthkit.tv/license-agreement-v1 Unless required by applicable law or agreed to in writing, the SDK distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ************************************************************************************/ using UnityEngine; using UnityEngine.XR; using System.Linq; using System; using System.Runtime.InteropServices; namespace Depthkit { public abstract class MeshSource : DataSource, IPropertyTransfer { #region SubMeshes public bool recalculateCurrentSurfaceTriangleCount = false; private SubMesh[] m_subMeshes; // Note: this is to serialize the max triangles, as the m_subMeshes array cannot be serialized. [SerializeField] private uint[] m_subMeshMaxTriangles; public SubMesh GetSubMesh(int index) { if (m_subMeshes == null || index >= m_subMeshes.Length) return null; return m_subMeshes[index]; } public T GetSubMesh(int index) where T : SubMesh { return GetSubMesh(index) as T; } public SubMesh CurrentSubMesh() { if (m_subMeshes == null || currentSubmeshIndex >= m_subMeshes.Length) return null; return m_subMeshes[currentSubmeshIndex]; } public T CurrentSubMesh() where T : SubMesh { return CurrentSubMesh() as T; } public void ReserveSubMeshes(int count) where T : SubMesh, new() { if (m_subMeshMaxTriangles == null || m_subMeshMaxTriangles.Length != count) { m_subMeshMaxTriangles = new uint[count]; for (int idx = 0; idx < count; idx++) { m_subMeshMaxTriangles[idx] = 0; } } if (m_subMeshes == null || m_subMeshes.Length != count) { if (m_subMeshes != null) { foreach (var sm in m_subMeshes) { sm.Release(); } } m_subMeshes = Enumerable.Range(0, count).Select(i => { return new T(); }).ToArray(); } for (int idx = 0; idx < count; idx++) { m_subMeshes[idx].source = this; m_subMeshes[idx].maxTriangles = m_subMeshMaxTriangles[idx]; m_subMeshes[idx].useTriangleMesh = m_useTriangleMesh; } } #endregion #region Properties public static class MeshSourceShaderIds { public static readonly int _RadialBiasPerspInMeters = Shader.PropertyToID("_RadialBiasPerspInMeters"); } protected bool m_forceStereo = false; protected uint m_currentSubmeshIndex; public uint currentSubmeshIndex { get { return m_currentSubmeshIndex; } set { m_currentSubmeshIndex = value; } } public ComputeBuffer triangleBuffer { get { return m_subMeshes == null ? null : m_subMeshes.Length <= m_currentSubmeshIndex ? null : m_subMeshes[m_currentSubmeshIndex].triangleBuffer; } } public ComputeBuffer triangleBufferDispatchIndirectArgs { get { return m_subMeshes == null ? null : m_subMeshes.Length <= m_currentSubmeshIndex ? null : m_subMeshes[m_currentSubmeshIndex].dispatchIndirectArgs; } } public ComputeBuffer triangleBufferDrawIndirectArgs { get { return m_subMeshes == null ? null : m_subMeshes.Length <= m_currentSubmeshIndex ? null : m_subMeshes[m_currentSubmeshIndex].drawIndirectArgs; } } public uint maxSurfaceTriangles { get { return m_subMeshes == null ? 0 : m_subMeshes.Length <= m_currentSubmeshIndex ? 0 : m_subMeshes[m_currentSubmeshIndex].maxTriangles; } set { if (m_subMeshes != null) { m_subMeshes[m_currentSubmeshIndex].maxTriangles = value; m_subMeshMaxTriangles[m_currentSubmeshIndex] = value; ScheduleResize(); ScheduleGenerate(); } } } [SerializeField, HideInInspector] bool m_useTriangleMesh = false; public bool useTriangleMesh { get { return m_useTriangleMesh; } set { if (m_subMeshes != null) { foreach (var persp in m_subMeshes) { persp.useTriangleMesh = value; } m_useTriangleMesh = value; } } } public TriangleMesh triangleMesh { get { return m_subMeshes == null ? null : m_subMeshes[m_currentSubmeshIndex].triangleMesh; } } #endregion #region RadialBias public const float radialBiasMin = 0.0f; public const float radialBiasMax = 2.50f; [Range(radialBiasMin, radialBiasMax)] public float radialBias = Metadata.DefaultReconstructionSettingsDefaults.depthBiasAdjustment; [SerializeField, HideInInspector] public float[] radialBiasPersp = null; // The datatype for the per perspective bias is a float4 because float arrays get pushed to the shader as 4 component float vectors. [SerializeField, HideInInspector] protected Vector4[] radialBiasPerspInMeters = null; void EnsureRadialBias() { // This way of sizing the radialBiasPersp array will cause multiperspective core clips to be sized to 12 if (radialBiasPersp == null || radialBiasPersp.Length != clip.metadata.perspectivesCount) { radialBiasPersp = new float[clip.metadata.perspectivesCount]; for (int i = 0; i < clip.metadata.perspectivesCount; ++i) { radialBiasPersp[i] = radialBias; } radialBiasPerspInMeters = null; } // This way of sizing the radialBiasPersp array will cause multiperspective core clips to be sized to 12 int size = (clip.metadata.perspectivesCount > 1) ? 12 : 1; if (radialBiasPerspInMeters == null || radialBiasPerspInMeters.Length != size) { Vector4 defaultVal = new Vector4(Util.cmToMeters(radialBias), 0, 0, 0); radialBiasPerspInMeters = new Vector4[size]; for (int i = 0; i < size; ++i) { radialBiasPerspInMeters[i] = defaultVal; } } } #endregion #region DataSource protected override void AcquireResources() { if (m_subMeshes != null) { int index = 0; foreach (var persp in m_subMeshes) { persp.EnsureBuffers(index++); } } if (!m_clip.isSetup) return; EnsureRadialBias(); base.AcquireResources(); } protected override void FreeResources() { if (m_subMeshes != null) { foreach (var submesh in m_subMeshes) { submesh.Release(); } } base.FreeResources(); } protected override bool CanGenerate() { if (m_subMeshes == null) return false; bool usesTriangleMesh = false; foreach (var persp in m_subMeshes) { if (persp.useTriangleMesh) usesTriangleMesh = true; } if (!usesTriangleMesh && (pauseDataGenerationWhenInvisible || pausePlayerWhenInvisible)) { CheckVisibility(); } return m_doGeneration; } public override bool OnSetup() { Util.ArgsBufferPrep.Setup(); //TODO this is a hack b/c the openVR plugin doesn't properly report XRSettings.stereoRenderingMode if (XRSettings.supportedDevices.Length > 0) { foreach (var dev in XRSettings.supportedDevices) { if (dev.Contains("OpenVR")) { m_forceStereo = true; break; } } } return true; } protected override bool OnResize() { if (!m_clip.isSetup || m_clip.metadata.textureWidth == 0 || m_clip.metadata.textureHeight == 0) return true; // This way of sizing the radialBiasPersp array will cause multiperspective core clips to be sized to 12 if (radialBiasPersp == null || radialBiasPersp.Length != clip.metadata.perspectivesCount) { radialBiasPersp = new float[clip.metadata.perspectivesCount]; for (int i = 0; i < clip.metadata.perspectivesCount; ++i) { radialBiasPersp[i] = radialBias; } radialBiasPerspInMeters = null; } // This way of sizing the radialBiasPersp array will cause multiperspective core clips to be sized to 12 int size = (clip.metadata.perspectivesCount > 1) ? 12 : 1; if (radialBiasPerspInMeters == null || radialBiasPerspInMeters.Length != size) { Vector4 defaultVal = new Vector4(Util.cmToMeters(radialBias), 0, 0, 0); radialBiasPerspInMeters = new Vector4[size]; for (int i = 0; i < size; ++i) { radialBiasPerspInMeters[i] = defaultVal; } } return true; } protected override void OnUpdate() { if (m_wasPlaying && !m_pausedFromRenderer) { CheckVisibility(); } else if (pauseDataGenerationWhenInvisible && !m_seenOnce) { CheckVisibility(); m_seenOnce = true; } if (clip != null && clip.isSetup && radialBiasPersp != null && radialBiasPerspInMeters != null) { // propagate radialBias value to per perspective for (int i = 0; i < radialBiasPersp.Length; ++i) { if (radialBiasPerspInMeters != null && radialBiasPerspInMeters.Length > 0) { radialBiasPerspInMeters[i].x = Util.cmToMeters(radialBias); } } } base.OnUpdate(); } protected override bool OnGenerate() { if (m_subMeshes == null) return false; foreach (var submesh in m_subMeshes) { submesh.CopyTriangleCount(); Util.ArgsBufferPrep.PrepareDrawArgs(submesh.trianglesCount, submesh.drawIndirectArgs, m_forceStereo); } if (recalculateCurrentSurfaceTriangleCount) { uint tempSubMeshIndex = currentSubmeshIndex; for (uint i = 0; i < m_subMeshes.Length; i++) { currentSubmeshIndex = i; maxSurfaceTriangles = m_subMeshes[i].calculateMaxTrianglesNeeded(); } currentSubmeshIndex = tempSubMeshIndex; recalculateCurrentSurfaceTriangleCount = false; ScheduleResize(); ScheduleGenerate(); } return true; } #endregion #region Visibility public bool pauseDataGenerationWhenInvisible = false; public bool pausePlayerWhenInvisible = false; protected bool m_wasPlaying = false; protected bool m_doGeneration = true; protected bool m_pausedFromRenderer = false; protected bool m_seenOnce = false; public virtual Bounds GetLocalBounds() { return clip != null ? new Bounds(clip.metadata.boundsCenter, clip.metadata.boundsSize) : new Bounds(Vector3.zero, Vector3.one); } public virtual Bounds GetWorldBounds() { Bounds bounds = GetLocalBounds(); Vector3 alt1 = bounds.center - new Vector3(-bounds.extents.x, -bounds.extents.y, bounds.extents.z); Vector3 alt2 = bounds.center + new Vector3(-bounds.extents.x, -bounds.extents.y, bounds.extents.z); return GeometryUtility.CalculateBounds(new Vector3[] { bounds.min, bounds.max, alt1, alt2 }, transform.localToWorldMatrix); } internal void Pause() { if (pausePlayerWhenInvisible) { if (clip.player.IsPlaying()) { m_wasPlaying = true; clip.player.Pause(); } } if (pauseDataGenerationWhenInvisible || pausePlayerWhenInvisible) { m_doGeneration = false; } } internal void Continue() { if (pausePlayerWhenInvisible) { if (!clip.player.IsPlaying() && m_wasPlaying) { clip.player.Play(); m_wasPlaying = false; } } if (pauseDataGenerationWhenInvisible || pausePlayerWhenInvisible) { m_doGeneration = true; } } private void OnBecameVisible() { Continue(); m_pausedFromRenderer = false; m_seenOnce = true; } private void OnBecameInvisible() { Pause(); m_pausedFromRenderer = true; } internal void CheckVisibility() { bool visible = Util.IsVisible(GetWorldBounds()); if (visible != m_doGeneration) //these should always be the same { if (visible) { Continue(); } else { Pause(); } } } #endregion #region IPropertyTransfer public virtual void SetProperties(ref ComputeShader compute, int kernel) { if (radialBiasPerspInMeters != null) compute.SetVectorArray(MeshSourceShaderIds._RadialBiasPerspInMeters, radialBiasPerspInMeters); if (m_subMeshes != null && m_subMeshes.Length > currentSubmeshIndex) m_subMeshes[currentSubmeshIndex].SetProperties(ref compute, kernel); } public virtual void SetProperties(ref Material material) { if (radialBiasPerspInMeters != null) material.SetVectorArray(MeshSourceShaderIds._RadialBiasPerspInMeters, radialBiasPerspInMeters); if (m_subMeshes != null && m_subMeshes.Length > currentSubmeshIndex) m_subMeshes[currentSubmeshIndex].SetProperties(ref material); } public virtual void SetProperties(ref Material material, ref MaterialPropertyBlock block) { if (radialBiasPerspInMeters != null) block.SetVectorArray(MeshSourceShaderIds._RadialBiasPerspInMeters, radialBiasPerspInMeters); if (m_subMeshes != null && m_subMeshes.Length > currentSubmeshIndex) m_subMeshes[currentSubmeshIndex].SetProperties(ref material, ref block); } #endregion } }