using UnityEngine; using UnityEngine.AI; using UnityEngine.Serialization; using UnityEngine.Rendering; using System.Collections.Generic; /// \brief A version of Unity's baked navmesh that is converted to a (serializable) component. This allows the navmesh /// used for Vive navigation to be separated form the AI Navmesh. ViveNavMesh also handles the rendering of the /// NavMesh grid in-game. [AddComponentMenu("Vive Teleporter/Vive Nav Mesh")] [ExecuteInEditMode] public class ViveNavMesh : MonoBehaviour { /// Material used for the floor mesh when the user is selecting a point to teleport to public Material GroundMaterial { get { return _GroundMaterial; } set { Material old = _GroundMaterial; _GroundMaterial = value; if(_GroundMaterial != null) _GroundMaterial.SetFloat(AlphaShaderID, GroundAlpha); if (old != _GroundMaterial) Cleanup(); } } private Material _GroundMaterial; [FormerlySerializedAs("_GroundMaterial")] [SerializeField] private Material _GroundMaterialSource; /// \brief The alpha (transparency) value of the rendered ground mesh) /// \sa GroundMaterial [Range(0,1)] public float GroundAlpha = 1.0f; private float LastGroundAlpha = 1.0f; private int AlphaShaderID = -1; public int LayerMask { get { return _LayerMask; } set { _LayerMask = value; } } [SerializeField] private int _LayerMask = 0; public bool IgnoreLayerMask { get { return _IgnoreLayerMask; } set { _IgnoreLayerMask = value; } } [SerializeField] private bool _IgnoreLayerMask = true; public int QueryTriggerInteraction { get { return _QueryTriggerInteraction; } set { _QueryTriggerInteraction = value; } } [SerializeField] private int _QueryTriggerInteraction = 0; /// A Mesh that represents the "Selectable" area of the world. This is converted from Unity's NavMesh in ViveNavMeshEditor public Mesh SelectableMesh { get { return _SelectableMesh; } set { _SelectableMesh = value; Cleanup(); } // Cleanup because we need to change the mesh inside command buffers } [SerializeField] [HideInInspector] private Mesh _SelectableMesh; /// \brief The border points of SelectableMesh. This is automatically generated in ViveNavMeshEditor. /// /// This is an array of Vector3 arrays, where each Vector3 array is the points in a polyline. These polylines combined /// describe the borders of SelectableMesh. We have to use BorderPointSets instead of a jagged Vector3[][] array because /// Unity can't serialize jagged arrays for some reason. public BorderPointSet[] SelectableMeshBorder { get { return _SelectableMeshBorder; } set { _SelectableMeshBorder = value; } } [SerializeField] [HideInInspector] private BorderPointSet[] _SelectableMeshBorder; [SerializeField] [HideInInspector] private int _NavAreaMask = ~0; // Initialize to all public bool IgnoreSlopedSurfaces { get { return _IgnoreSlopedSurfaces; } } [SerializeField] private bool _IgnoreSlopedSurfaces = true; public float SampleRadius { get { return _SampleRadius; } } [SerializeField] private float _SampleRadius = 0.25f; public NavmeshDewarpingMethod DewarpingMethod { get { return _DewarpingMethod; } } [SerializeField] private NavmeshDewarpingMethod _DewarpingMethod = NavmeshDewarpingMethod.None; private Dictionary cameras = new Dictionary(); void Start () { if (SelectableMesh == null) SelectableMesh = new Mesh(); if (_SelectableMeshBorder == null) _SelectableMeshBorder = new BorderPointSet[0]; AlphaShaderID = Shader.PropertyToID("_Alpha"); if(_GroundMaterialSource != null) GroundMaterial = new Material(_GroundMaterialSource); if (GroundAlpha != LastGroundAlpha && GroundMaterial != null) { GroundMaterial.SetFloat(AlphaShaderID, GroundAlpha); LastGroundAlpha = GroundAlpha; } #if UNITY_EDITOR UnityEditor.SceneView.RepaintAll(); #endif } private void Cleanup() { foreach (var cam in cameras) { if (cam.Key) { cam.Key.RemoveCommandBuffer(CameraEvent.AfterForwardOpaque, cam.Value); } } cameras.Clear(); } public void OnEnable() { Cleanup(); } public void OnDisable() { Cleanup(); } /// \brief Casts a ray against the Navmesh and attempts to calculate the ray's worldspace intersection with it. /// /// This uses Physics raycasts to perform the raycast calculation, so the teleport surface must have a collider /// on it. /// /// \param p1 First (origin) point of ray /// \param p2 Last (end) point of ray /// \param pointOnNavmesh If the raycast hit something on the navmesh. /// \param hitPoint If hit, the point of the hit. Otherwise zero. /// \param normal If hit, the normal of the hit surface. Otherwise (0, 1, 0) /// /// \return If the raycast hit something. public bool Linecast(Vector3 p1, Vector3 p2, out bool pointOnNavmesh, out Vector3 hitPoint, out Vector3 normal) { RaycastHit hit; Vector3 dir = p2 - p1; float dist = dir.magnitude; dir /= dist; if(Physics.Raycast(p1, dir, out hit, dist, _IgnoreLayerMask ? ~_LayerMask : _LayerMask, (QueryTriggerInteraction) _QueryTriggerInteraction)) { normal = hit.normal; if (Vector3.Dot(Vector3.up, hit.normal) < 0.99f && _IgnoreSlopedSurfaces) { pointOnNavmesh = false; hitPoint = hit.point; return true; } hitPoint = hit.point; NavMeshHit navHit; pointOnNavmesh = NavMesh.SamplePosition(hitPoint, out navHit, _SampleRadius, _NavAreaMask); return true; } pointOnNavmesh = false; hitPoint = Vector3.zero; normal = Vector3.up; return false; } } [System.Serializable] public class BorderPointSet { public Vector3[] Points; public BorderPointSet(Vector3[] Points) { this.Points = Points; } } [System.Serializable] public enum NavmeshDewarpingMethod { None, RoundToVoxelSize, RaycastDownward }