SW 중심대학 OSS GIT 서버 박건태, 이승준, 고기완, 이준호 새로운 배포
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

494 lines
16 KiB

using System;
using System.Collections.Generic;
using UnityEngine.XR.ARSubsystems;
#if UNITY_2019_3_OR_NEWER
using LegacyMeshId = UnityEngine.XR.MeshId;
#else
using LegacyMeshId = UnityEngine.Experimental.XR.TrackableId;
using MeshInfo = UnityEngine.Experimental.XR.MeshInfo;
using XRMeshSubsystem = UnityEngine.Experimental.XR.XRMeshSubsystem;
using XRMeshSubsystemDescriptor = UnityEngine.Experimental.XR.XRMeshSubsystemDescriptor;
using MeshChangeState = UnityEngine.Experimental.XR.MeshChangeState;
using MeshVertexAttributes = UnityEngine.Experimental.XR.MeshVertexAttributes;
using MeshGenerationStatus = UnityEngine.Experimental.XR.MeshGenerationStatus;
using MeshGenerationResult = UnityEngine.Experimental.XR.MeshGenerationResult;
#endif
#if !UNITY_2019_2_OR_NEWER
using SubsystemManager = UnityEngine.Experimental.SubsystemManager;
#endif
namespace UnityEngine.XR.ARFoundation
{
/// <summary>
/// A manager for triangle meshes generated by an AR device.
/// Creates, updates, and removes <c>GameObject</c>s in response to
/// the environment. For each mesh, a <see cref="meshPrefab"/> is
/// instantiated which must contain at least a <c>MeshFilter</c>.
/// If the <see cref="meshPrefab"/>'s <c>GameObject</c> also has a
/// <c>MeshCollider</c>, then a physics mesh is generated asynchronously,
/// without blocking the main thread.
/// </summary>
[DefaultExecutionOrder(ARUpdateOrder.k_MeshManager)]
[HelpURL("https://docs.unity3d.com/Packages/com.unity.xr.arfoundation@3.0/api/UnityEngine.XR.ARFoundation.ARMeshManager.html")]
[DisallowMultipleComponent]
public class ARMeshManager : MonoBehaviour
{
[SerializeField]
[Tooltip("The prefab to be instantiated for each generated mesh. MeshColliders are processed asynchronously and do not block the main thread.")]
MeshFilter m_MeshPrefab;
/// <summary>
/// A prefab to be instantiated for each generated mesh. The prefab must have at least a
/// <c>MeshFilter</c> component on it. If it also has a <c>MeshCollider</c> component, the
/// physics bounding volume data will be generated asynchronously. This does not block the
/// main thread, but may take longer to process.
/// </summary>
public MeshFilter meshPrefab
{
get { return m_MeshPrefab; }
set { m_MeshPrefab = value; }
}
[SerializeField]
[Tooltip("The density of the generated mesh [0..1]. 1 will be highly tesselated while 0 will be very low.")]
[Range(0, 1)]
float m_Density = 0.5f;
/// <summary>
/// The density of the generated mesh [0..1]. 1 will be densely tesselated,
/// while 0 will have the lowest supported tesselation.
/// </summary>
public float density
{
get { return m_Density; }
set
{
if (value < 0f || value > 1f)
throw new ArgumentOutOfRangeException(nameof(value), value, "Mesh density must be between 0 and 1, inclusive.");
if (m_Density == value)
return;
m_Density = value;
if (m_Subsystem != null)
m_Subsystem.meshDensity = m_Density;
}
}
[SerializeField]
[Tooltip("If enabled, a normal is requested for each vertex.")]
bool m_Normals = true;
/// <summary>
/// If <c>True</c>, requests a normal for each vertex in generated meshes.
/// </summary>
public bool normals
{
get { return m_Normals; }
set { m_Normals = value; }
}
[SerializeField]
[Tooltip("If enabled, a tangent is requested for each vertex.")]
bool m_Tangents;
/// <summary>
/// If <c>True</c>, requests a tangent for each vertex in generated meshes.
/// </summary>
public bool tangents
{
get { return m_Tangents; }
set { m_Tangents = value; }
}
[SerializeField]
[Tooltip("If enabled, a UV texture coordinate is requested for each vertex.")]
bool m_TextureCoordinates;
/// <summary>
/// If <c>True</c>, requests a texture coordinate for each vertex in generated meshes.
/// </summary>
public bool textureCoordinates
{
get { return m_TextureCoordinates; }
set { m_TextureCoordinates = value; }
}
[SerializeField]
[Tooltip("If enabled, a color value is requested for each vertex.")]
bool m_Colors;
/// <summary>
/// If <c>True</c>, requests a color value for each vertex in generated meshes.
/// </summary>
public bool colors
{
get { return m_Colors; }
set { m_Colors = value; }
}
[SerializeField]
[Tooltip("The number of meshes to process concurrently. Higher values require more CPU time.")]
int m_ConcurrentQueueSize = 4;
/// <summary>
/// The number of meshes to process concurrently. Meshes are processed on a background
/// thread. Higher numbers will require additional CPU time.
/// </summary>
public int concurrentQueueSize
{
get { return m_ConcurrentQueueSize; }
set { m_ConcurrentQueueSize = value; }
}
/// <summary>
/// Invoked whenever meshes have changed (been added, updated, or removed).
/// </summary>
public event Action<ARMeshesChangedEventArgs> meshesChanged;
/// <summary>
/// The <c>XRMeshSubsystem</c> used by this component to generate meshes.
/// </summary>
public XRMeshSubsystem subsystem
{
get { return m_Subsystem; }
}
/// <summary>
/// Returns a collection of <c>MeshFilter</c>s representing meshes generated by this component.
/// </summary>
public IList<MeshFilter> meshes
{
get { return m_Meshes.Values; }
}
/// <summary>
/// Destroys all generated meshes and ignores any pending meshes.
/// </summary>
public void DestroyAllMeshes()
{
m_Pending.Clear();
m_Generating.Clear();
foreach (var meshFilter in meshes)
{
if (meshFilter != null)
Destroy(meshFilter.gameObject);
}
m_Meshes.Clear();
}
ARSessionOrigin GetSessionOrigin()
{
if (transform.parent == null)
return null;
return transform.parent.GetComponent<ARSessionOrigin>();
}
#if UNITY_EDITOR
void Reset()
{
if (GetSessionOrigin() != null)
transform.localScale = Vector3.one * 10f;
}
void OnValidate()
{
if (GetSessionOrigin() == null)
{
UnityEditor.EditorUtility.DisplayDialog(
"Hierarchy not allowed",
$"An {nameof(ARMeshManager)} must be a child of an {nameof(ARSessionOrigin)}.",
"Remove Component");
UnityEditor.EditorApplication.delayCall += ()=>
{
DestroyImmediate(this);
};
}
}
#endif
void SetBoundingVolume()
{
m_Subsystem.SetBoundingVolume(transform.localPosition, transform.localScale);
transform.hasChanged = false;
}
void OnEnable()
{
if (GetSessionOrigin() == null)
{
enabled = false;
throw new InvalidOperationException($"An {nameof(ARMeshManager)} must be a child of an {nameof(ARSessionOrigin)}.");
}
if (m_Subsystem == null)
m_Subsystem = CreateSubsystem();
if (m_Subsystem != null)
{
m_Subsystem.meshDensity = m_Density;
SetBoundingVolume();
m_Subsystem.Start();
}
else
{
enabled = false;
}
}
void OnDrawGizmosSelected()
{
Gizmos.color = new Color(0, .5f, 0, .35f);
Gizmos.matrix = transform.localToWorldMatrix;
Gizmos.DrawCube(Vector3.zero, Vector3.one);
}
void Update()
{
if (m_Subsystem != null)
{
if (transform.hasChanged)
SetBoundingVolume();
UpdateMeshInfos();
if (m_MeshPrefab != null)
Generate();
}
// Invoke user callbacks
try
{
if (meshesChanged != null && (m_Added.Count + m_Updated.Count + m_Removed.Count > 0))
{
meshesChanged(new ARMeshesChangedEventArgs(m_Added, m_Updated, m_Removed));
}
}
finally
{
// Make sure we clear the internal lists if user code throws an exception
m_Added.Clear();
m_Updated.Clear();
foreach (var meshFilter in m_Removed)
{
if (meshFilter != null)
Destroy(meshFilter.gameObject);
}
m_Removed.Clear();
}
}
void Generate()
{
var vertexAttributes = MeshVertexAttributes.None;
if (m_Normals)
vertexAttributes |= MeshVertexAttributes.Normals;
if (m_Tangents)
vertexAttributes |= MeshVertexAttributes.Tangents;
if (m_TextureCoordinates)
vertexAttributes |= MeshVertexAttributes.UVs;
if (m_Colors)
vertexAttributes |= MeshVertexAttributes.Colors;
while ((m_Generating.Count < m_ConcurrentQueueSize) &&
m_Pending.TryDequeue(m_Generating, out MeshInfo meshInfo))
{
var meshId = meshInfo.MeshId;
var meshFilter = GetOrCreateMeshFilter(GetTrackableId(meshId));
var meshCollider = meshFilter.GetComponent<MeshCollider>();
var mesh = (meshFilter.sharedMesh != null) ? meshFilter.sharedMesh : meshFilter.mesh;
m_Generating.Add(meshId, meshInfo);
m_Subsystem.GenerateMeshAsync(
meshInfo.MeshId,
mesh,
meshCollider,
vertexAttributes,
m_OnMeshGeneratedDelegate);
}
}
void OnMeshGenerated(MeshGenerationResult result)
{
if (!m_Generating.TryGetValue(result.MeshId, out MeshInfo meshInfo))
return;
m_Generating.Remove(result.MeshId);
if (result.Status != MeshGenerationStatus.Success)
return;
if (!m_Meshes.TryGetValue(GetTrackableId(result.MeshId), out MeshFilter meshFilter) || (meshFilter == null))
return;
meshFilter.gameObject.SetActive(true);
switch (meshInfo.ChangeState)
{
case MeshChangeState.Added:
m_Added.Add(meshFilter);
break;
case MeshChangeState.Updated:
m_Updated.Add(meshFilter);
break;
// Removed/unchanged meshes don't get generated.
default:
break;
}
}
void UpdateMeshInfos()
{
if (m_Subsystem.TryGetMeshInfos(s_MeshInfos))
{
foreach (var meshInfo in s_MeshInfos)
{
switch (meshInfo.ChangeState)
{
case MeshChangeState.Added:
case MeshChangeState.Updated:
m_Pending.EnqueueUnique(meshInfo);
break;
case MeshChangeState.Removed:
// Remove from processing queues
m_Pending.Remove(meshInfo.MeshId);
m_Generating.Remove(meshInfo.MeshId);
// Add to list of removed meshes
var trackableId = GetTrackableId(meshInfo.MeshId);
if (m_Meshes.TryGetValue(trackableId, out MeshFilter meshFilter))
{
m_Meshes.Remove(trackableId);
if (meshFilter != null)
m_Removed.Add(meshFilter);
}
break;
default:
break;
}
}
}
}
void OnDisable()
{
if (m_Subsystem != null)
m_Subsystem.Stop();
}
void OnDestroy()
{
if (m_Subsystem != null)
m_Subsystem.Destroy();
m_Subsystem = null;
}
MeshFilter GetOrCreateMeshFilter(TrackableId trackableId)
{
// If the mesh filter is Destroyed by user code, then meshFilter will compare
// equal with null. In that case, we want to recreate it.
if (m_Meshes.TryGetValue(trackableId, out MeshFilter meshFilter) && (meshFilter != null))
return meshFilter;
var sessionOrigin = GetSessionOrigin();
meshFilter = (sessionOrigin == null) ?
Instantiate(m_MeshPrefab) :
Instantiate(m_MeshPrefab, sessionOrigin.trackablesParent);
meshFilter.gameObject.name = $"Mesh {trackableId.ToString()}";
// The GameObject should start life inactive until we've populated it
meshFilter.gameObject.SetActive(false);
m_Meshes[trackableId] = meshFilter;
return meshFilter;
}
internal static unsafe TrackableId GetTrackableId(LegacyMeshId trackableId)
{
return *(TrackableId*)&trackableId;
}
internal static unsafe LegacyMeshId GetLegacyMeshId(TrackableId trackableId)
{
return *(LegacyMeshId*)&trackableId;
}
void Awake()
{
m_Added = new List<MeshFilter>();
m_Updated = new List<MeshFilter>();
m_Removed = new List<MeshFilter>();
m_Pending = new MeshQueue();
m_Generating = new Dictionary<LegacyMeshId, MeshInfo>();
m_Meshes = new SortedList<TrackableId, MeshFilter>(s_TrackableIdComparer);
m_OnMeshGeneratedDelegate = new Action<MeshGenerationResult>(OnMeshGenerated);
}
XRMeshSubsystem CreateSubsystem()
{
SubsystemManager.GetSubsystemDescriptors(s_SubsystemDescriptors);
if (s_SubsystemDescriptors.Count > 0)
{
var descriptor = s_SubsystemDescriptors[0];
if (s_SubsystemDescriptors.Count > 1)
Debug.LogWarning($"Multiple {nameof(XRMeshSubsystem)} found. Using {descriptor.id}");
return descriptor.Create();
}
else
{
return null;
}
}
class TrackableIdComparer : IComparer<TrackableId>
{
public int Compare(TrackableId trackableIdA, TrackableId trackableIdB)
{
if (trackableIdA.subId1 == trackableIdB.subId1)
{
return trackableIdA.subId2.CompareTo(trackableIdB.subId2);
}
else
{
return trackableIdA.subId1.CompareTo(trackableIdB.subId1);
}
}
}
List<MeshFilter> m_Added;
List<MeshFilter> m_Updated;
List<MeshFilter> m_Removed;
MeshQueue m_Pending;
Dictionary<LegacyMeshId, MeshInfo> m_Generating;
SortedList<TrackableId, MeshFilter> m_Meshes;
Action<MeshGenerationResult> m_OnMeshGeneratedDelegate;
XRMeshSubsystem m_Subsystem;
static TrackableIdComparer s_TrackableIdComparer = new TrackableIdComparer();
static List<MeshInfo> s_MeshInfos = new List<MeshInfo>();
static List<XRMeshSubsystemDescriptor> s_SubsystemDescriptors =
new List<XRMeshSubsystemDescriptor>();
}
}