From dce9671af2564eb5093fd8b157ccc9cb9cde697d Mon Sep 17 00:00:00 2001 From: Alex38Lyon <55714436+Alex38Lyon@users.noreply.github.com> Date: Tue, 3 Jun 2025 11:35:23 +0200 Subject: [PATCH] mod --- Assets/NavMeshComponents/Editor.meta | 8 + .../Editor/NavMeshAssetManager.cs | 325 ++++++++++++ .../Editor/NavMeshAssetManager.cs.meta | 11 + .../Editor/NavMeshComponentsEditor.asmdef | 16 + .../NavMeshComponentsEditor.asmdef.meta | 7 + .../Editor/NavMeshComponentsGUIUtility.cs | 258 ++++++++++ .../NavMeshComponentsGUIUtility.cs.meta | 12 + Assets/NavMeshComponents/LICENSE | 21 + Assets/NavMeshComponents/LICENSE.meta | 7 + Assets/NavMeshComponents/README.md | 3 + Assets/NavMeshComponents/README.md.meta | 7 + Assets/NavMeshComponents/Scripts.meta | 8 + .../Scripts/NavMeshComponents.asmdef | 12 + .../Scripts/NavMeshComponents.asmdef.meta | 7 + .../NavMeshComponents/Scripts/NavMeshLink.cs | 172 +++++++ .../Scripts/NavMeshLink.cs.meta | 12 + .../Scripts/NavMeshModifier.cs | 54 ++ .../Scripts/NavMeshModifier.cs.meta | 12 + .../Scripts/NavMeshModifierVolume.cs | 54 ++ .../Scripts/NavMeshModifierVolume.cs.meta | 12 + .../Scripts/NavMeshSurface.cs | 486 ++++++++++++++++++ .../Scripts/NavMeshSurface.cs.meta | 12 + 22 files changed, 1516 insertions(+) create mode 100644 Assets/NavMeshComponents/Editor.meta create mode 100644 Assets/NavMeshComponents/Editor/NavMeshAssetManager.cs create mode 100644 Assets/NavMeshComponents/Editor/NavMeshAssetManager.cs.meta create mode 100644 Assets/NavMeshComponents/Editor/NavMeshComponentsEditor.asmdef create mode 100644 Assets/NavMeshComponents/Editor/NavMeshComponentsEditor.asmdef.meta create mode 100644 Assets/NavMeshComponents/Editor/NavMeshComponentsGUIUtility.cs create mode 100644 Assets/NavMeshComponents/Editor/NavMeshComponentsGUIUtility.cs.meta create mode 100644 Assets/NavMeshComponents/LICENSE create mode 100644 Assets/NavMeshComponents/LICENSE.meta create mode 100644 Assets/NavMeshComponents/README.md create mode 100644 Assets/NavMeshComponents/README.md.meta create mode 100644 Assets/NavMeshComponents/Scripts.meta create mode 100644 Assets/NavMeshComponents/Scripts/NavMeshComponents.asmdef create mode 100644 Assets/NavMeshComponents/Scripts/NavMeshComponents.asmdef.meta create mode 100644 Assets/NavMeshComponents/Scripts/NavMeshLink.cs create mode 100644 Assets/NavMeshComponents/Scripts/NavMeshLink.cs.meta create mode 100644 Assets/NavMeshComponents/Scripts/NavMeshModifier.cs create mode 100644 Assets/NavMeshComponents/Scripts/NavMeshModifier.cs.meta create mode 100644 Assets/NavMeshComponents/Scripts/NavMeshModifierVolume.cs create mode 100644 Assets/NavMeshComponents/Scripts/NavMeshModifierVolume.cs.meta create mode 100644 Assets/NavMeshComponents/Scripts/NavMeshSurface.cs create mode 100644 Assets/NavMeshComponents/Scripts/NavMeshSurface.cs.meta diff --git a/Assets/NavMeshComponents/Editor.meta b/Assets/NavMeshComponents/Editor.meta new file mode 100644 index 0000000..13ec6a0 --- /dev/null +++ b/Assets/NavMeshComponents/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b4f8c2ee775248c4e88d8bf73622cb99 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/Editor/NavMeshAssetManager.cs b/Assets/NavMeshComponents/Editor/NavMeshAssetManager.cs new file mode 100644 index 0000000..0af5839 --- /dev/null +++ b/Assets/NavMeshComponents/Editor/NavMeshAssetManager.cs @@ -0,0 +1,325 @@ +using System.Collections.Generic; +using System.IO; +using UnityEditor.Experimental.SceneManagement; +using UnityEditor.SceneManagement; +using UnityEngine.AI; +using UnityEngine; + +namespace UnityEditor.AI +{ + public class NavMeshAssetManager : ScriptableSingleton + { + internal struct AsyncBakeOperation + { + public NavMeshSurface Surface; + public NavMeshData BakeData; + public AsyncOperation BakeOperation; + } + + List m_BakeOperations = new List(); + internal List GetBakeOperations() { return m_BakeOperations; } + + struct SavedPrefabNavMeshData + { + public NavMeshSurface Surface; + public NavMeshData NavMeshData; + } + + List m_PrefabNavMeshDataAssets = new List(); + + static string GetAndEnsureTargetPath(NavMeshSurface surface) + { + // Create directory for the asset if it does not exist yet. + var activeScenePath = surface.gameObject.scene.path; + + var targetPath = "Assets"; + if (!string.IsNullOrEmpty(activeScenePath)) + { + targetPath = Path.Combine(Path.GetDirectoryName(activeScenePath), Path.GetFileNameWithoutExtension(activeScenePath)); + } + else + { + var prefabStage = PrefabStageUtility.GetPrefabStage(surface.gameObject); + var isPartOfPrefab = prefabStage != null && prefabStage.IsPartOfPrefabContents(surface.gameObject); + if (isPartOfPrefab && !string.IsNullOrEmpty(prefabStage.assetPath)) + { + var prefabDirectoryName = Path.GetDirectoryName(prefabStage.assetPath); + if (!string.IsNullOrEmpty(prefabDirectoryName)) + targetPath = prefabDirectoryName; + } + } + if (!Directory.Exists(targetPath)) + Directory.CreateDirectory(targetPath); + return targetPath; + } + + static void CreateNavMeshAsset(NavMeshSurface surface) + { + var targetPath = GetAndEnsureTargetPath(surface); + + var combinedAssetPath = Path.Combine(targetPath, "NavMesh-" + surface.name + ".asset"); + combinedAssetPath = AssetDatabase.GenerateUniqueAssetPath(combinedAssetPath); + AssetDatabase.CreateAsset(surface.navMeshData, combinedAssetPath); + } + + NavMeshData GetNavMeshAssetToDelete(NavMeshSurface navSurface) + { + if (PrefabUtility.IsPartOfPrefabInstance(navSurface) && !PrefabUtility.IsPartOfModelPrefab(navSurface)) + { + // Don't allow deleting the asset belonging to the prefab parent + var parentSurface = PrefabUtility.GetCorrespondingObjectFromSource(navSurface) as NavMeshSurface; + if (parentSurface && navSurface.navMeshData == parentSurface.navMeshData) + return null; + } + + // Do not delete the NavMeshData asset referenced from a prefab until the prefab is saved + var prefabStage = PrefabStageUtility.GetPrefabStage(navSurface.gameObject); + var isPartOfPrefab = prefabStage != null && prefabStage.IsPartOfPrefabContents(navSurface.gameObject); + if (isPartOfPrefab && IsCurrentPrefabNavMeshDataStored(navSurface)) + return null; + + return navSurface.navMeshData; + } + + void ClearSurface(NavMeshSurface navSurface) + { + var hasNavMeshData = navSurface.navMeshData != null; + StoreNavMeshDataIfInPrefab(navSurface); + + var assetToDelete = GetNavMeshAssetToDelete(navSurface); + navSurface.RemoveData(); + + if (hasNavMeshData) + { + SetNavMeshData(navSurface, null); + EditorSceneManager.MarkSceneDirty(navSurface.gameObject.scene); + } + + if (assetToDelete) + AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(assetToDelete)); + } + + public void StartBakingSurfaces(UnityEngine.Object[] surfaces) + { + // Remove first to avoid double registration of the callback + EditorApplication.update -= UpdateAsyncBuildOperations; + EditorApplication.update += UpdateAsyncBuildOperations; + + foreach (NavMeshSurface surf in surfaces) + { + StoreNavMeshDataIfInPrefab(surf); + + var oper = new AsyncBakeOperation(); + + oper.BakeData = InitializeBakeData(surf); + oper.BakeOperation = surf.UpdateNavMesh(oper.BakeData); + oper.Surface = surf; + + m_BakeOperations.Add(oper); + } + } + + static NavMeshData InitializeBakeData(NavMeshSurface surface) + { + var emptySources = new List(); + var emptyBounds = new Bounds(); + return UnityEngine.AI.NavMeshBuilder.BuildNavMeshData(surface.GetBuildSettings(), emptySources, emptyBounds + , surface.transform.position, surface.transform.rotation); + } + + void UpdateAsyncBuildOperations() + { + foreach (var oper in m_BakeOperations) + { + if (oper.Surface == null || oper.BakeOperation == null) + continue; + + if (oper.BakeOperation.isDone) + { + var surface = oper.Surface; + var delete = GetNavMeshAssetToDelete(surface); + if (delete != null) + AssetDatabase.DeleteAsset(AssetDatabase.GetAssetPath(delete)); + + surface.RemoveData(); + SetNavMeshData(surface, oper.BakeData); + + if (surface.isActiveAndEnabled) + surface.AddData(); + CreateNavMeshAsset(surface); + EditorSceneManager.MarkSceneDirty(surface.gameObject.scene); + } + } + m_BakeOperations.RemoveAll(o => o.BakeOperation == null || o.BakeOperation.isDone); + if (m_BakeOperations.Count == 0) + EditorApplication.update -= UpdateAsyncBuildOperations; + } + + public bool IsSurfaceBaking(NavMeshSurface surface) + { + if (surface == null) + return false; + + foreach (var oper in m_BakeOperations) + { + if (oper.Surface == null || oper.BakeOperation == null) + continue; + + if (oper.Surface == surface) + return true; + } + + return false; + } + + public void ClearSurfaces(UnityEngine.Object[] surfaces) + { + foreach (NavMeshSurface s in surfaces) + ClearSurface(s); + } + + static void SetNavMeshData(NavMeshSurface navSurface, NavMeshData navMeshData) + { + var so = new SerializedObject(navSurface); + var navMeshDataProperty = so.FindProperty("m_NavMeshData"); + navMeshDataProperty.objectReferenceValue = navMeshData; + so.ApplyModifiedPropertiesWithoutUndo(); + } + + void StoreNavMeshDataIfInPrefab(NavMeshSurface surfaceToStore) + { + var prefabStage = PrefabStageUtility.GetPrefabStage(surfaceToStore.gameObject); + var isPartOfPrefab = prefabStage != null && prefabStage.IsPartOfPrefabContents(surfaceToStore.gameObject); + if (!isPartOfPrefab) + return; + + // check if data has already been stored for this surface + foreach (var storedAssetInfo in m_PrefabNavMeshDataAssets) + if (storedAssetInfo.Surface == surfaceToStore) + return; + + if (m_PrefabNavMeshDataAssets.Count == 0) + { + PrefabStage.prefabSaving -= DeleteStoredNavMeshDataAssetsForOwnedSurfaces; + PrefabStage.prefabSaving += DeleteStoredNavMeshDataAssetsForOwnedSurfaces; + + PrefabStage.prefabStageClosing -= ForgetUnsavedNavMeshDataChanges; + PrefabStage.prefabStageClosing += ForgetUnsavedNavMeshDataChanges; + } + + var isDataOwner = true; + if (PrefabUtility.IsPartOfPrefabInstance(surfaceToStore) && !PrefabUtility.IsPartOfModelPrefab(surfaceToStore)) + { + var basePrefabSurface = PrefabUtility.GetCorrespondingObjectFromSource(surfaceToStore) as NavMeshSurface; + isDataOwner = basePrefabSurface == null || surfaceToStore.navMeshData != basePrefabSurface.navMeshData; + } + m_PrefabNavMeshDataAssets.Add(new SavedPrefabNavMeshData { Surface = surfaceToStore, NavMeshData = isDataOwner ? surfaceToStore.navMeshData : null }); + } + + bool IsCurrentPrefabNavMeshDataStored(NavMeshSurface surface) + { + if (surface == null) + return false; + + foreach (var storedAssetInfo in m_PrefabNavMeshDataAssets) + { + if (storedAssetInfo.Surface == surface) + return storedAssetInfo.NavMeshData == surface.navMeshData; + } + + return false; + } + + void DeleteStoredNavMeshDataAssetsForOwnedSurfaces(GameObject gameObjectInPrefab) + { + // Debug.LogFormat("DeleteStoredNavMeshDataAsset() when saving prefab {0}", gameObjectInPrefab.name); + + var surfaces = gameObjectInPrefab.GetComponentsInChildren(true); + foreach (var surface in surfaces) + DeleteStoredPrefabNavMeshDataAsset(surface); + } + + void DeleteStoredPrefabNavMeshDataAsset(NavMeshSurface surface) + { + for (var i = m_PrefabNavMeshDataAssets.Count - 1; i >= 0; i--) + { + var storedAssetInfo = m_PrefabNavMeshDataAssets[i]; + if (storedAssetInfo.Surface == surface) + { + var storedNavMeshData = storedAssetInfo.NavMeshData; + if (storedNavMeshData != null && storedNavMeshData != surface.navMeshData) + { + var assetPath = AssetDatabase.GetAssetPath(storedNavMeshData); + AssetDatabase.DeleteAsset(assetPath); + } + + m_PrefabNavMeshDataAssets.RemoveAt(i); + break; + } + } + + if (m_PrefabNavMeshDataAssets.Count == 0) + { + PrefabStage.prefabSaving -= DeleteStoredNavMeshDataAssetsForOwnedSurfaces; + PrefabStage.prefabStageClosing -= ForgetUnsavedNavMeshDataChanges; + } + } + + void ForgetUnsavedNavMeshDataChanges(PrefabStage prefabStage) + { + // Debug.Log("On prefab closing - forget about this object's surfaces and stop caring about prefab saving"); + + if (prefabStage == null) + return; + + var allSurfacesInPrefab = prefabStage.prefabContentsRoot.GetComponentsInChildren(true); + NavMeshSurface surfaceInPrefab = null; + var index = 0; + do + { + if (allSurfacesInPrefab.Length > 0) + surfaceInPrefab = allSurfacesInPrefab[index]; + + for (var i = m_PrefabNavMeshDataAssets.Count - 1; i >= 0; i--) + { + var storedPrefabInfo = m_PrefabNavMeshDataAssets[i]; + if (storedPrefabInfo.Surface == null) + { + // Debug.LogFormat("A surface from the prefab got deleted after it has baked a new NavMesh but it hasn't saved it. Now the unsaved asset gets deleted. ({0})", storedPrefabInfo.navMeshData); + + // surface got deleted, thus delete its initial NavMeshData asset + if (storedPrefabInfo.NavMeshData != null) + { + var assetPath = AssetDatabase.GetAssetPath(storedPrefabInfo.NavMeshData); + AssetDatabase.DeleteAsset(assetPath); + } + + m_PrefabNavMeshDataAssets.RemoveAt(i); + } + else if (surfaceInPrefab != null && storedPrefabInfo.Surface == surfaceInPrefab) + { + //Debug.LogFormat("The surface {0} from the prefab was storing the original navmesh data and now will be forgotten", surfaceInPrefab); + + var baseSurface = PrefabUtility.GetCorrespondingObjectFromSource(surfaceInPrefab) as NavMeshSurface; + if (baseSurface == null || surfaceInPrefab.navMeshData != baseSurface.navMeshData) + { + var assetPath = AssetDatabase.GetAssetPath(surfaceInPrefab.navMeshData); + AssetDatabase.DeleteAsset(assetPath); + + //Debug.LogFormat("The surface {0} from the prefab has baked new NavMeshData but did not save this change so the asset has been now deleted. ({1})", + // surfaceInPrefab, assetPath); + } + + m_PrefabNavMeshDataAssets.RemoveAt(i); + } + } + } while (++index < allSurfacesInPrefab.Length); + + if (m_PrefabNavMeshDataAssets.Count == 0) + { + PrefabStage.prefabSaving -= DeleteStoredNavMeshDataAssetsForOwnedSurfaces; + PrefabStage.prefabStageClosing -= ForgetUnsavedNavMeshDataChanges; + } + } + } +} diff --git a/Assets/NavMeshComponents/Editor/NavMeshAssetManager.cs.meta b/Assets/NavMeshComponents/Editor/NavMeshAssetManager.cs.meta new file mode 100644 index 0000000..ace95c7 --- /dev/null +++ b/Assets/NavMeshComponents/Editor/NavMeshAssetManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7081cfb45d93f064db511881f87cb0b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/Editor/NavMeshComponentsEditor.asmdef b/Assets/NavMeshComponents/Editor/NavMeshComponentsEditor.asmdef new file mode 100644 index 0000000..3c9827e --- /dev/null +++ b/Assets/NavMeshComponents/Editor/NavMeshComponentsEditor.asmdef @@ -0,0 +1,16 @@ +{ + "name": "NavMeshComponentsEditor", + "references": [ + "NavMeshComponents" + ], + "optionalUnityReferences": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Assets/NavMeshComponents/Editor/NavMeshComponentsEditor.asmdef.meta b/Assets/NavMeshComponents/Editor/NavMeshComponentsEditor.asmdef.meta new file mode 100644 index 0000000..0112273 --- /dev/null +++ b/Assets/NavMeshComponents/Editor/NavMeshComponentsEditor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5c7deb0338552a549a4a74d064f07701 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/Editor/NavMeshComponentsGUIUtility.cs b/Assets/NavMeshComponents/Editor/NavMeshComponentsGUIUtility.cs new file mode 100644 index 0000000..514cfb9 --- /dev/null +++ b/Assets/NavMeshComponents/Editor/NavMeshComponentsGUIUtility.cs @@ -0,0 +1,258 @@ +using UnityEngine; +using UnityEngine.AI; + +namespace UnityEditor.AI +{ + public static class NavMeshComponentsGUIUtility + { + public static void AreaPopup(string labelName, SerializedProperty areaProperty) + { + var areaIndex = -1; + var areaNames = NavMesh.GetAreaNames(); + for (var i = 0; i < areaNames.Length; i++) + { + var areaValue = NavMesh.GetAreaFromName(areaNames[i]); + if (areaValue == areaProperty.intValue) + areaIndex = i; + } + ArrayUtility.Add(ref areaNames, ""); + ArrayUtility.Add(ref areaNames, "Open Area Settings..."); + + var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight); + EditorGUI.BeginProperty(rect, GUIContent.none, areaProperty); + + EditorGUI.BeginChangeCheck(); + areaIndex = EditorGUI.Popup(rect, labelName, areaIndex, areaNames); + + if (EditorGUI.EndChangeCheck()) + { + if (areaIndex >= 0 && areaIndex < areaNames.Length - 2) + areaProperty.intValue = NavMesh.GetAreaFromName(areaNames[areaIndex]); + else if (areaIndex == areaNames.Length - 1) + NavMeshEditorHelpers.OpenAreaSettings(); + } + + EditorGUI.EndProperty(); + } + + public static void AgentTypePopup(string labelName, SerializedProperty agentTypeID) + { + var index = -1; + var count = NavMesh.GetSettingsCount(); + var agentTypeNames = new string[count + 2]; + for (var i = 0; i < count; i++) + { + var id = NavMesh.GetSettingsByIndex(i).agentTypeID; + var name = NavMesh.GetSettingsNameFromID(id); + agentTypeNames[i] = name; + if (id == agentTypeID.intValue) + index = i; + } + agentTypeNames[count] = ""; + agentTypeNames[count + 1] = "Open Agent Settings..."; + + bool validAgentType = index != -1; + if (!validAgentType) + { + EditorGUILayout.HelpBox("Agent Type invalid.", MessageType.Warning); + } + + var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight); + EditorGUI.BeginProperty(rect, GUIContent.none, agentTypeID); + + EditorGUI.BeginChangeCheck(); + index = EditorGUI.Popup(rect, labelName, index, agentTypeNames); + if (EditorGUI.EndChangeCheck()) + { + if (index >= 0 && index < count) + { + var id = NavMesh.GetSettingsByIndex(index).agentTypeID; + agentTypeID.intValue = id; + } + else if (index == count + 1) + { + NavMeshEditorHelpers.OpenAgentSettings(-1); + } + } + + EditorGUI.EndProperty(); + } + + // Agent mask is a set (internally array/list) of agentTypeIDs. + // It is used to describe which agents modifiers apply to. + // There is a special case of "None" which is an empty array. + // There is a special case of "All" which is an array of length 1, and value of -1. + public static void AgentMaskPopup(string labelName, SerializedProperty agentMask) + { + // Contents of the dropdown box. + string popupContent = ""; + + if (agentMask.hasMultipleDifferentValues) + popupContent = "\u2014"; + else + popupContent = GetAgentMaskLabelName(agentMask); + + var content = new GUIContent(popupContent); + var popupRect = GUILayoutUtility.GetRect(content, EditorStyles.popup); + + EditorGUI.BeginProperty(popupRect, GUIContent.none, agentMask); + popupRect = EditorGUI.PrefixLabel(popupRect, 0, new GUIContent(labelName)); + bool pressed = GUI.Button(popupRect, content, EditorStyles.popup); + + if (pressed) + { + var show = !agentMask.hasMultipleDifferentValues; + var showNone = show && agentMask.arraySize == 0; + var showAll = show && IsAll(agentMask); + + var menu = new GenericMenu(); + menu.AddItem(new GUIContent("None"), showNone, SetAgentMaskNone, agentMask); + menu.AddItem(new GUIContent("All"), showAll, SetAgentMaskAll, agentMask); + menu.AddSeparator(""); + + var count = NavMesh.GetSettingsCount(); + for (var i = 0; i < count; i++) + { + var id = NavMesh.GetSettingsByIndex(i).agentTypeID; + var sname = NavMesh.GetSettingsNameFromID(id); + + var showSelected = show && AgentMaskHasSelectedAgentTypeID(agentMask, id); + var userData = new object[] { agentMask, id, !showSelected }; + menu.AddItem(new GUIContent(sname), showSelected, ToggleAgentMaskItem, userData); + } + + menu.DropDown(popupRect); + } + + EditorGUI.EndProperty(); + } + + public static GameObject CreateAndSelectGameObject(string suggestedName, GameObject parent) + { + var parentTransform = parent != null ? parent.transform : null; + var uniqueName = GameObjectUtility.GetUniqueNameForSibling(parentTransform, suggestedName); + var child = new GameObject(uniqueName); + + Undo.RegisterCreatedObjectUndo(child, "Create " + uniqueName); + if (parentTransform != null) + Undo.SetTransformParent(child.transform, parentTransform, "Parent " + uniqueName); + + Selection.activeGameObject = child; + + return child; + } + + static bool IsAll(SerializedProperty agentMask) + { + return agentMask.arraySize == 1 && agentMask.GetArrayElementAtIndex(0).intValue == -1; + } + + static void ToggleAgentMaskItem(object userData) + { + var args = (object[])userData; + var agentMask = (SerializedProperty)args[0]; + var agentTypeID = (int)args[1]; + var value = (bool)args[2]; + + ToggleAgentMaskItem(agentMask, agentTypeID, value); + } + + static void ToggleAgentMaskItem(SerializedProperty agentMask, int agentTypeID, bool value) + { + if (agentMask.hasMultipleDifferentValues) + { + agentMask.ClearArray(); + agentMask.serializedObject.ApplyModifiedProperties(); + } + + // Find which index this agent type is in the agentMask array. + int idx = -1; + for (var j = 0; j < agentMask.arraySize; j++) + { + var elem = agentMask.GetArrayElementAtIndex(j); + if (elem.intValue == agentTypeID) + idx = j; + } + + // Handle "All" special case. + if (IsAll(agentMask)) + { + agentMask.DeleteArrayElementAtIndex(0); + } + + // Toggle value. + if (value) + { + if (idx == -1) + { + agentMask.InsertArrayElementAtIndex(agentMask.arraySize); + agentMask.GetArrayElementAtIndex(agentMask.arraySize - 1).intValue = agentTypeID; + } + } + else + { + if (idx != -1) + { + agentMask.DeleteArrayElementAtIndex(idx); + } + } + + agentMask.serializedObject.ApplyModifiedProperties(); + } + + static void SetAgentMaskNone(object data) + { + var agentMask = (SerializedProperty)data; + agentMask.ClearArray(); + agentMask.serializedObject.ApplyModifiedProperties(); + } + + static void SetAgentMaskAll(object data) + { + var agentMask = (SerializedProperty)data; + agentMask.ClearArray(); + agentMask.InsertArrayElementAtIndex(0); + agentMask.GetArrayElementAtIndex(0).intValue = -1; + agentMask.serializedObject.ApplyModifiedProperties(); + } + + static string GetAgentMaskLabelName(SerializedProperty agentMask) + { + if (agentMask.arraySize == 0) + return "None"; + + if (IsAll(agentMask)) + return "All"; + + if (agentMask.arraySize <= 3) + { + var labelName = ""; + for (var j = 0; j < agentMask.arraySize; j++) + { + var elem = agentMask.GetArrayElementAtIndex(j); + var settingsName = NavMesh.GetSettingsNameFromID(elem.intValue); + if (string.IsNullOrEmpty(settingsName)) + continue; + + if (labelName.Length > 0) + labelName += ", "; + labelName += settingsName; + } + return labelName; + } + + return "Mixed..."; + } + + static bool AgentMaskHasSelectedAgentTypeID(SerializedProperty agentMask, int agentTypeID) + { + for (var j = 0; j < agentMask.arraySize; j++) + { + var elem = agentMask.GetArrayElementAtIndex(j); + if (elem.intValue == agentTypeID) + return true; + } + return false; + } + } +} diff --git a/Assets/NavMeshComponents/Editor/NavMeshComponentsGUIUtility.cs.meta b/Assets/NavMeshComponents/Editor/NavMeshComponentsGUIUtility.cs.meta new file mode 100644 index 0000000..651b8d3 --- /dev/null +++ b/Assets/NavMeshComponents/Editor/NavMeshComponentsGUIUtility.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: cd87d77a7e9dee24bbb6eaf7b8ac12da +timeCreated: 1480524815 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/LICENSE b/Assets/NavMeshComponents/LICENSE new file mode 100644 index 0000000..ea2052f --- /dev/null +++ b/Assets/NavMeshComponents/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016, Unity Technologies + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Assets/NavMeshComponents/LICENSE.meta b/Assets/NavMeshComponents/LICENSE.meta new file mode 100644 index 0000000..bece98c --- /dev/null +++ b/Assets/NavMeshComponents/LICENSE.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3a22fd34ec9ff1c42ac4394fad8eb365 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/README.md b/Assets/NavMeshComponents/README.md new file mode 100644 index 0000000..900aab6 --- /dev/null +++ b/Assets/NavMeshComponents/README.md @@ -0,0 +1,3 @@ +Version used in this project: + +https://github.com/Unity-Technologies/NavMeshComponents/releases/tag/2019.3.0f6_2 diff --git a/Assets/NavMeshComponents/README.md.meta b/Assets/NavMeshComponents/README.md.meta new file mode 100644 index 0000000..3a3aaa4 --- /dev/null +++ b/Assets/NavMeshComponents/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b0f4eda5652dc4841bb446c4721eaec1 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/Scripts.meta b/Assets/NavMeshComponents/Scripts.meta new file mode 100644 index 0000000..87f1330 --- /dev/null +++ b/Assets/NavMeshComponents/Scripts.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8d3679086bc6c5f4495f15a6b970c7ba +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/Scripts/NavMeshComponents.asmdef b/Assets/NavMeshComponents/Scripts/NavMeshComponents.asmdef new file mode 100644 index 0000000..a54b5c2 --- /dev/null +++ b/Assets/NavMeshComponents/Scripts/NavMeshComponents.asmdef @@ -0,0 +1,12 @@ +{ + "name": "NavMeshComponents", + "references": [], + "optionalUnityReferences": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Assets/NavMeshComponents/Scripts/NavMeshComponents.asmdef.meta b/Assets/NavMeshComponents/Scripts/NavMeshComponents.asmdef.meta new file mode 100644 index 0000000..653b911 --- /dev/null +++ b/Assets/NavMeshComponents/Scripts/NavMeshComponents.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e44d436f4cd5ae341be3cbd8ce8c447b +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/Scripts/NavMeshLink.cs b/Assets/NavMeshComponents/Scripts/NavMeshLink.cs new file mode 100644 index 0000000..d9b9519 --- /dev/null +++ b/Assets/NavMeshComponents/Scripts/NavMeshLink.cs @@ -0,0 +1,172 @@ +using System.Collections.Generic; + +namespace UnityEngine.AI +{ + [ExecuteInEditMode] + [DefaultExecutionOrder(-101)] + [AddComponentMenu("Navigation/NavMeshLink", 33)] + [HelpURL("https://github.com/Unity-Technologies/NavMeshComponents#documentation-draft")] + public class NavMeshLink : MonoBehaviour + { + [SerializeField] + int m_AgentTypeID; + public int agentTypeID { get { return m_AgentTypeID; } set { m_AgentTypeID = value; UpdateLink(); } } + + [SerializeField] + Vector3 m_StartPoint = new Vector3(0.0f, 0.0f, -2.5f); + public Vector3 startPoint { get { return m_StartPoint; } set { m_StartPoint = value; UpdateLink(); } } + + [SerializeField] + Vector3 m_EndPoint = new Vector3(0.0f, 0.0f, 2.5f); + public Vector3 endPoint { get { return m_EndPoint; } set { m_EndPoint = value; UpdateLink(); } } + + [SerializeField] + float m_Width; + public float width { get { return m_Width; } set { m_Width = value; UpdateLink(); } } + + [SerializeField] + int m_CostModifier = -1; + public int costModifier { get { return m_CostModifier; } set { m_CostModifier = value; UpdateLink(); } } + + [SerializeField] + bool m_Bidirectional = true; + public bool bidirectional { get { return m_Bidirectional; } set { m_Bidirectional = value; UpdateLink(); } } + + [SerializeField] + bool m_AutoUpdatePosition; + public bool autoUpdate { get { return m_AutoUpdatePosition; } set { SetAutoUpdate(value); } } + + [SerializeField] + int m_Area; + public int area { get { return m_Area; } set { m_Area = value; UpdateLink(); } } + + NavMeshLinkInstance m_LinkInstance = new NavMeshLinkInstance(); + + Vector3 m_LastPosition = Vector3.zero; + Quaternion m_LastRotation = Quaternion.identity; + + static readonly List s_Tracked = new List(); + + void OnEnable() + { + AddLink(); + if (m_AutoUpdatePosition && NavMesh.IsLinkValid(m_LinkInstance)) + AddTracking(this); + } + + void OnDisable() + { + RemoveTracking(this); + NavMesh.RemoveLink(m_LinkInstance); + } + + public void UpdateLink() + { + NavMesh.RemoveLink(m_LinkInstance); + AddLink(); + } + + static void AddTracking(NavMeshLink link) + { +#if UNITY_EDITOR + if (s_Tracked.Contains(link)) + { + Debug.LogError("Link is already tracked: " + link); + return; + } +#endif + + if (s_Tracked.Count == 0) + NavMesh.onPreUpdate += UpdateTrackedInstances; + + s_Tracked.Add(link); + } + + static void RemoveTracking(NavMeshLink link) + { + s_Tracked.Remove(link); + + if (s_Tracked.Count == 0) + NavMesh.onPreUpdate -= UpdateTrackedInstances; + } + + void SetAutoUpdate(bool value) + { + if (m_AutoUpdatePosition == value) + return; + m_AutoUpdatePosition = value; + if (value) + AddTracking(this); + else + RemoveTracking(this); + } + + void AddLink() + { +#if UNITY_EDITOR + if (NavMesh.IsLinkValid(m_LinkInstance)) + { + Debug.LogError("Link is already added: " + this); + return; + } +#endif + + var link = new NavMeshLinkData(); + link.startPosition = m_StartPoint; + link.endPosition = m_EndPoint; + link.width = m_Width; + link.costModifier = m_CostModifier; + link.bidirectional = m_Bidirectional; + link.area = m_Area; + link.agentTypeID = m_AgentTypeID; + m_LinkInstance = NavMesh.AddLink(link, transform.position, transform.rotation); + if (NavMesh.IsLinkValid(m_LinkInstance)) + NavMesh.SetLinkOwner(m_LinkInstance, this); + + m_LastPosition = transform.position; + m_LastRotation = transform.rotation; + } + + bool HasTransformChanged() + { + if (m_LastPosition != transform.position) return true; + if (m_LastRotation != transform.rotation) return true; + return false; + } + + void OnDidApplyAnimationProperties() + { + UpdateLink(); + } + + static void UpdateTrackedInstances() + { + foreach (var instance in s_Tracked) + { + if (instance.HasTransformChanged()) + instance.UpdateLink(); + } + } + +#if UNITY_EDITOR + void OnValidate() + { + m_Width = Mathf.Max(0.0f, m_Width); + + if (!NavMesh.IsLinkValid(m_LinkInstance)) + return; + + UpdateLink(); + + if (!m_AutoUpdatePosition) + { + RemoveTracking(this); + } + else if (!s_Tracked.Contains(this)) + { + AddTracking(this); + } + } +#endif + } +} diff --git a/Assets/NavMeshComponents/Scripts/NavMeshLink.cs.meta b/Assets/NavMeshComponents/Scripts/NavMeshLink.cs.meta new file mode 100644 index 0000000..e8915e7 --- /dev/null +++ b/Assets/NavMeshComponents/Scripts/NavMeshLink.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 944aa5b74b8898a40ba726e5da1aeae4 +timeCreated: 1477924439 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 92f4afa3e25264f5b964937ccea49ff2, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/Scripts/NavMeshModifier.cs b/Assets/NavMeshComponents/Scripts/NavMeshModifier.cs new file mode 100644 index 0000000..f32e80c --- /dev/null +++ b/Assets/NavMeshComponents/Scripts/NavMeshModifier.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; + +namespace UnityEngine.AI +{ + [ExecuteInEditMode] + [AddComponentMenu("Navigation/NavMeshModifier", 32)] + [HelpURL("https://github.com/Unity-Technologies/NavMeshComponents#documentation-draft")] + public class NavMeshModifier : MonoBehaviour + { + [SerializeField] + bool m_OverrideArea; + public bool overrideArea { get { return m_OverrideArea; } set { m_OverrideArea = value; } } + + [SerializeField] + int m_Area; + public int area { get { return m_Area; } set { m_Area = value; } } + + [SerializeField] + bool m_IgnoreFromBuild; + public bool ignoreFromBuild { get { return m_IgnoreFromBuild; } set { m_IgnoreFromBuild = value; } } + + // List of agent types the modifier is applied for. + // Special values: empty == None, m_AffectedAgents[0] =-1 == All. + [SerializeField] + List m_AffectedAgents = new List(new int[] { -1 }); // Default value is All + + static readonly List s_NavMeshModifiers = new List(); + + public static List activeModifiers + { + get { return s_NavMeshModifiers; } + } + + void OnEnable() + { + if (!s_NavMeshModifiers.Contains(this)) + s_NavMeshModifiers.Add(this); + } + + void OnDisable() + { + s_NavMeshModifiers.Remove(this); + } + + public bool AffectsAgentType(int agentTypeID) + { + if (m_AffectedAgents.Count == 0) + return false; + if (m_AffectedAgents[0] == -1) + return true; + return m_AffectedAgents.IndexOf(agentTypeID) != -1; + } + } +} diff --git a/Assets/NavMeshComponents/Scripts/NavMeshModifier.cs.meta b/Assets/NavMeshComponents/Scripts/NavMeshModifier.cs.meta new file mode 100644 index 0000000..242d819 --- /dev/null +++ b/Assets/NavMeshComponents/Scripts/NavMeshModifier.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: 90068f5c8ba86d3499874221895df26b +timeCreated: 1477924411 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: cc7b9475dbddf4f9088d327d6e10ab77, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/Scripts/NavMeshModifierVolume.cs b/Assets/NavMeshComponents/Scripts/NavMeshModifierVolume.cs new file mode 100644 index 0000000..faa7e63 --- /dev/null +++ b/Assets/NavMeshComponents/Scripts/NavMeshModifierVolume.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; + +namespace UnityEngine.AI +{ + [ExecuteInEditMode] + [AddComponentMenu("Navigation/NavMeshModifierVolume", 31)] + [HelpURL("https://github.com/Unity-Technologies/NavMeshComponents#documentation-draft")] + public class NavMeshModifierVolume : MonoBehaviour + { + [SerializeField] + Vector3 m_Size = new Vector3(4.0f, 3.0f, 4.0f); + public Vector3 size { get { return m_Size; } set { m_Size = value; } } + + [SerializeField] + Vector3 m_Center = new Vector3(0, 1.0f, 0); + public Vector3 center { get { return m_Center; } set { m_Center = value; } } + + [SerializeField] + int m_Area; + public int area { get { return m_Area; } set { m_Area = value; } } + + // List of agent types the modifier is applied for. + // Special values: empty == None, m_AffectedAgents[0] =-1 == All. + [SerializeField] + List m_AffectedAgents = new List(new int[] { -1 }); // Default value is All + + static readonly List s_NavMeshModifiers = new List(); + + public static List activeModifiers + { + get { return s_NavMeshModifiers; } + } + + void OnEnable() + { + if (!s_NavMeshModifiers.Contains(this)) + s_NavMeshModifiers.Add(this); + } + + void OnDisable() + { + s_NavMeshModifiers.Remove(this); + } + + public bool AffectsAgentType(int agentTypeID) + { + if (m_AffectedAgents.Count == 0) + return false; + if (m_AffectedAgents[0] == -1) + return true; + return m_AffectedAgents.IndexOf(agentTypeID) != -1; + } + } +} diff --git a/Assets/NavMeshComponents/Scripts/NavMeshModifierVolume.cs.meta b/Assets/NavMeshComponents/Scripts/NavMeshModifierVolume.cs.meta new file mode 100644 index 0000000..e79a9b4 --- /dev/null +++ b/Assets/NavMeshComponents/Scripts/NavMeshModifierVolume.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: bb0dd6fc9f577b440b7c21586a75ace2 +timeCreated: 1477924430 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: cc7b9475dbddf4f9088d327d6e10ab77, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/NavMeshComponents/Scripts/NavMeshSurface.cs b/Assets/NavMeshComponents/Scripts/NavMeshSurface.cs new file mode 100644 index 0000000..33b1e94 --- /dev/null +++ b/Assets/NavMeshComponents/Scripts/NavMeshSurface.cs @@ -0,0 +1,486 @@ +using System.Collections.Generic; +#if UNITY_EDITOR +using UnityEditor; +using UnityEditor.SceneManagement; +#endif + +namespace UnityEngine.AI +{ + public enum CollectObjects + { + All = 0, + Volume = 1, + Children = 2, + } + + [ExecuteAlways] + [DefaultExecutionOrder(-102)] + [AddComponentMenu("Navigation/NavMeshSurface", 30)] + [HelpURL("https://github.com/Unity-Technologies/NavMeshComponents#documentation-draft")] + public class NavMeshSurface : MonoBehaviour + { + [SerializeField] + int m_AgentTypeID; + public int agentTypeID { get { return m_AgentTypeID; } set { m_AgentTypeID = value; } } + + [SerializeField] + CollectObjects m_CollectObjects = CollectObjects.All; + public CollectObjects collectObjects { get { return m_CollectObjects; } set { m_CollectObjects = value; } } + + [SerializeField] + Vector3 m_Size = new Vector3(10.0f, 10.0f, 10.0f); + public Vector3 size { get { return m_Size; } set { m_Size = value; } } + + [SerializeField] + Vector3 m_Center = new Vector3(0, 2.0f, 0); + public Vector3 center { get { return m_Center; } set { m_Center = value; } } + + [SerializeField] + LayerMask m_LayerMask = ~0; + public LayerMask layerMask { get { return m_LayerMask; } set { m_LayerMask = value; } } + + [SerializeField] + NavMeshCollectGeometry m_UseGeometry = NavMeshCollectGeometry.RenderMeshes; + public NavMeshCollectGeometry useGeometry { get { return m_UseGeometry; } set { m_UseGeometry = value; } } + + [SerializeField] + int m_DefaultArea; + public int defaultArea { get { return m_DefaultArea; } set { m_DefaultArea = value; } } + + [SerializeField] + bool m_IgnoreNavMeshAgent = true; + public bool ignoreNavMeshAgent { get { return m_IgnoreNavMeshAgent; } set { m_IgnoreNavMeshAgent = value; } } + + [SerializeField] + bool m_IgnoreNavMeshObstacle = true; + public bool ignoreNavMeshObstacle { get { return m_IgnoreNavMeshObstacle; } set { m_IgnoreNavMeshObstacle = value; } } + + [SerializeField] + bool m_OverrideTileSize; + public bool overrideTileSize { get { return m_OverrideTileSize; } set { m_OverrideTileSize = value; } } + [SerializeField] + int m_TileSize = 256; + public int tileSize { get { return m_TileSize; } set { m_TileSize = value; } } + [SerializeField] + bool m_OverrideVoxelSize; + public bool overrideVoxelSize { get { return m_OverrideVoxelSize; } set { m_OverrideVoxelSize = value; } } + [SerializeField] + float m_VoxelSize; + public float voxelSize { get { return m_VoxelSize; } set { m_VoxelSize = value; } } + + // Currently not supported advanced options + [SerializeField] + bool m_BuildHeightMesh; + public bool buildHeightMesh { get { return m_BuildHeightMesh; } set { m_BuildHeightMesh = value; } } + + // Reference to whole scene navmesh data asset. + [UnityEngine.Serialization.FormerlySerializedAs("m_BakedNavMeshData")] + [SerializeField] + NavMeshData m_NavMeshData; + public NavMeshData navMeshData { get { return m_NavMeshData; } set { m_NavMeshData = value; } } + + // Do not serialize - runtime only state. + NavMeshDataInstance m_NavMeshDataInstance; + Vector3 m_LastPosition = Vector3.zero; + Quaternion m_LastRotation = Quaternion.identity; + + static readonly List s_NavMeshSurfaces = new List(); + + public static List activeSurfaces + { + get { return s_NavMeshSurfaces; } + } + + void OnEnable() + { + Register(this); + AddData(); + } + + void OnDisable() + { + RemoveData(); + Unregister(this); + } + + public void AddData() + { +#if UNITY_EDITOR + var isInPreviewScene = EditorSceneManager.IsPreviewSceneObject(this); + var isPrefab = isInPreviewScene || EditorUtility.IsPersistent(this); + if (isPrefab) + { + //Debug.LogFormat("NavMeshData from {0}.{1} will not be added to the NavMesh world because the gameObject is a prefab.", + // gameObject.name, name); + return; + } +#endif + if (m_NavMeshDataInstance.valid) + return; + + if (m_NavMeshData != null) + { + m_NavMeshDataInstance = NavMesh.AddNavMeshData(m_NavMeshData, transform.position, transform.rotation); + m_NavMeshDataInstance.owner = this; + } + + m_LastPosition = transform.position; + m_LastRotation = transform.rotation; + } + + public void RemoveData() + { + m_NavMeshDataInstance.Remove(); + m_NavMeshDataInstance = new NavMeshDataInstance(); + } + + public NavMeshBuildSettings GetBuildSettings() + { + var buildSettings = NavMesh.GetSettingsByID(m_AgentTypeID); + if (buildSettings.agentTypeID == -1) + { + Debug.LogWarning("No build settings for agent type ID " + agentTypeID, this); + buildSettings.agentTypeID = m_AgentTypeID; + } + + if (overrideTileSize) + { + buildSettings.overrideTileSize = true; + buildSettings.tileSize = tileSize; + } + if (overrideVoxelSize) + { + buildSettings.overrideVoxelSize = true; + buildSettings.voxelSize = voxelSize; + } + return buildSettings; + } + + public void BuildNavMesh() + { + var sources = CollectSources(); + + // Use unscaled bounds - this differs in behaviour from e.g. collider components. + // But is similar to reflection probe - and since navmesh data has no scaling support - it is the right choice here. + var sourcesBounds = new Bounds(m_Center, Abs(m_Size)); + if (m_CollectObjects == CollectObjects.All || m_CollectObjects == CollectObjects.Children) + { + sourcesBounds = CalculateWorldBounds(sources); + } + + var data = NavMeshBuilder.BuildNavMeshData(GetBuildSettings(), + sources, sourcesBounds, transform.position, transform.rotation); + + if (data != null) + { + data.name = gameObject.name; + RemoveData(); + m_NavMeshData = data; + if (isActiveAndEnabled) + AddData(); + } + } + + public AsyncOperation UpdateNavMesh(NavMeshData data) + { + var sources = CollectSources(); + + // Use unscaled bounds - this differs in behaviour from e.g. collider components. + // But is similar to reflection probe - and since navmesh data has no scaling support - it is the right choice here. + var sourcesBounds = new Bounds(m_Center, Abs(m_Size)); + if (m_CollectObjects == CollectObjects.All || m_CollectObjects == CollectObjects.Children) + sourcesBounds = CalculateWorldBounds(sources); + + return NavMeshBuilder.UpdateNavMeshDataAsync(data, GetBuildSettings(), sources, sourcesBounds); + } + + static void Register(NavMeshSurface surface) + { +#if UNITY_EDITOR + var isInPreviewScene = EditorSceneManager.IsPreviewSceneObject(surface); + var isPrefab = isInPreviewScene || EditorUtility.IsPersistent(surface); + if (isPrefab) + { + //Debug.LogFormat("NavMeshData from {0}.{1} will not be added to the NavMesh world because the gameObject is a prefab.", + // surface.gameObject.name, surface.name); + return; + } +#endif + if (s_NavMeshSurfaces.Count == 0) + NavMesh.onPreUpdate += UpdateActive; + + if (!s_NavMeshSurfaces.Contains(surface)) + s_NavMeshSurfaces.Add(surface); + } + + static void Unregister(NavMeshSurface surface) + { + s_NavMeshSurfaces.Remove(surface); + + if (s_NavMeshSurfaces.Count == 0) + NavMesh.onPreUpdate -= UpdateActive; + } + + static void UpdateActive() + { + for (var i = 0; i < s_NavMeshSurfaces.Count; ++i) + s_NavMeshSurfaces[i].UpdateDataIfTransformChanged(); + } + + void AppendModifierVolumes(ref List sources) + { +#if UNITY_EDITOR + var myStage = StageUtility.GetStageHandle(gameObject); + if (!myStage.IsValid()) + return; +#endif + // Modifiers + List modifiers; + if (m_CollectObjects == CollectObjects.Children) + { + modifiers = new List(GetComponentsInChildren()); + modifiers.RemoveAll(x => !x.isActiveAndEnabled); + } + else + { + modifiers = NavMeshModifierVolume.activeModifiers; + } + + foreach (var m in modifiers) + { + if ((m_LayerMask & (1 << m.gameObject.layer)) == 0) + continue; + if (!m.AffectsAgentType(m_AgentTypeID)) + continue; +#if UNITY_EDITOR + if (!myStage.Contains(m.gameObject)) + continue; +#endif + var mcenter = m.transform.TransformPoint(m.center); + var scale = m.transform.lossyScale; + var msize = new Vector3(m.size.x * Mathf.Abs(scale.x), m.size.y * Mathf.Abs(scale.y), m.size.z * Mathf.Abs(scale.z)); + + var src = new NavMeshBuildSource(); + src.shape = NavMeshBuildSourceShape.ModifierBox; + src.transform = Matrix4x4.TRS(mcenter, m.transform.rotation, Vector3.one); + src.size = msize; + src.area = m.area; + sources.Add(src); + } + } + + List CollectSources() + { + var sources = new List(); + var markups = new List(); + + List modifiers; + if (m_CollectObjects == CollectObjects.Children) + { + modifiers = new List(GetComponentsInChildren()); + modifiers.RemoveAll(x => !x.isActiveAndEnabled); + } + else + { + modifiers = NavMeshModifier.activeModifiers; + } + + foreach (var m in modifiers) + { + if ((m_LayerMask & (1 << m.gameObject.layer)) == 0) + continue; + if (!m.AffectsAgentType(m_AgentTypeID)) + continue; + var markup = new NavMeshBuildMarkup(); + markup.root = m.transform; + markup.overrideArea = m.overrideArea; + markup.area = m.area; + markup.ignoreFromBuild = m.ignoreFromBuild; + markups.Add(markup); + } + +#if UNITY_EDITOR + if (!EditorApplication.isPlaying) + { + if (m_CollectObjects == CollectObjects.All) + { + UnityEditor.AI.NavMeshEditorHelpers.CollectSourcesInStage( + null, m_LayerMask, m_UseGeometry, m_DefaultArea, markups, gameObject.scene, sources); + } + else if (m_CollectObjects == CollectObjects.Children) + { + UnityEditor.AI.NavMeshEditorHelpers.CollectSourcesInStage( + transform, m_LayerMask, m_UseGeometry, m_DefaultArea, markups, gameObject.scene, sources); + } + else if (m_CollectObjects == CollectObjects.Volume) + { + Matrix4x4 localToWorld = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one); + var worldBounds = GetWorldBounds(localToWorld, new Bounds(m_Center, m_Size)); + + UnityEditor.AI.NavMeshEditorHelpers.CollectSourcesInStage( + worldBounds, m_LayerMask, m_UseGeometry, m_DefaultArea, markups, gameObject.scene, sources); + } + } + else +#endif + { + if (m_CollectObjects == CollectObjects.All) + { + NavMeshBuilder.CollectSources(null, m_LayerMask, m_UseGeometry, m_DefaultArea, markups, sources); + } + else if (m_CollectObjects == CollectObjects.Children) + { + NavMeshBuilder.CollectSources(transform, m_LayerMask, m_UseGeometry, m_DefaultArea, markups, sources); + } + else if (m_CollectObjects == CollectObjects.Volume) + { + Matrix4x4 localToWorld = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one); + var worldBounds = GetWorldBounds(localToWorld, new Bounds(m_Center, m_Size)); + NavMeshBuilder.CollectSources(worldBounds, m_LayerMask, m_UseGeometry, m_DefaultArea, markups, sources); + } + } + + if (m_IgnoreNavMeshAgent) + sources.RemoveAll((x) => (x.component != null && x.component.gameObject.GetComponent() != null)); + + if (m_IgnoreNavMeshObstacle) + sources.RemoveAll((x) => (x.component != null && x.component.gameObject.GetComponent() != null)); + + AppendModifierVolumes(ref sources); + + return sources; + } + + static Vector3 Abs(Vector3 v) + { + return new Vector3(Mathf.Abs(v.x), Mathf.Abs(v.y), Mathf.Abs(v.z)); + } + + static Bounds GetWorldBounds(Matrix4x4 mat, Bounds bounds) + { + var absAxisX = Abs(mat.MultiplyVector(Vector3.right)); + var absAxisY = Abs(mat.MultiplyVector(Vector3.up)); + var absAxisZ = Abs(mat.MultiplyVector(Vector3.forward)); + var worldPosition = mat.MultiplyPoint(bounds.center); + var worldSize = absAxisX * bounds.size.x + absAxisY * bounds.size.y + absAxisZ * bounds.size.z; + return new Bounds(worldPosition, worldSize); + } + + Bounds CalculateWorldBounds(List sources) + { + // Use the unscaled matrix for the NavMeshSurface + Matrix4x4 worldToLocal = Matrix4x4.TRS(transform.position, transform.rotation, Vector3.one); + worldToLocal = worldToLocal.inverse; + + var result = new Bounds(); + foreach (var src in sources) + { + switch (src.shape) + { + case NavMeshBuildSourceShape.Mesh: + { + var m = src.sourceObject as Mesh; + result.Encapsulate(GetWorldBounds(worldToLocal * src.transform, m.bounds)); + break; + } + case NavMeshBuildSourceShape.Terrain: + { + // Terrain pivot is lower/left corner - shift bounds accordingly + var t = src.sourceObject as TerrainData; + result.Encapsulate(GetWorldBounds(worldToLocal * src.transform, new Bounds(0.5f * t.size, t.size))); + break; + } + case NavMeshBuildSourceShape.Box: + case NavMeshBuildSourceShape.Sphere: + case NavMeshBuildSourceShape.Capsule: + case NavMeshBuildSourceShape.ModifierBox: + result.Encapsulate(GetWorldBounds(worldToLocal * src.transform, new Bounds(Vector3.zero, src.size))); + break; + } + } + // Inflate the bounds a bit to avoid clipping co-planar sources + result.Expand(0.1f); + return result; + } + + bool HasTransformChanged() + { + if (m_LastPosition != transform.position) return true; + if (m_LastRotation != transform.rotation) return true; + return false; + } + + void UpdateDataIfTransformChanged() + { + if (HasTransformChanged()) + { + RemoveData(); + AddData(); + } + } + +#if UNITY_EDITOR + bool UnshareNavMeshAsset() + { + // Nothing to unshare + if (m_NavMeshData == null) + return false; + + // Prefab parent owns the asset reference + var isInPreviewScene = EditorSceneManager.IsPreviewSceneObject(this); + var isPersistentObject = EditorUtility.IsPersistent(this); + if (isInPreviewScene || isPersistentObject) + return false; + + // An instance can share asset reference only with its prefab parent + var prefab = UnityEditor.PrefabUtility.GetCorrespondingObjectFromSource(this) as NavMeshSurface; + if (prefab != null && prefab.navMeshData == navMeshData) + return false; + + // Don't allow referencing an asset that's assigned to another surface + for (var i = 0; i < s_NavMeshSurfaces.Count; ++i) + { + var surface = s_NavMeshSurfaces[i]; + if (surface != this && surface.m_NavMeshData == m_NavMeshData) + return true; + } + + // Asset is not referenced by known surfaces + return false; + } + + void OnValidate() + { + if (UnshareNavMeshAsset()) + { + Debug.LogWarning("Duplicating NavMeshSurface does not duplicate the referenced navmesh data", this); + m_NavMeshData = null; + } + + var settings = NavMesh.GetSettingsByID(m_AgentTypeID); + if (settings.agentTypeID != -1) + { + // When unchecking the override control, revert to automatic value. + const float kMinVoxelSize = 0.01f; + if (!m_OverrideVoxelSize) + m_VoxelSize = settings.agentRadius / 3.0f; + if (m_VoxelSize < kMinVoxelSize) + m_VoxelSize = kMinVoxelSize; + + // When unchecking the override control, revert to default value. + const int kMinTileSize = 16; + const int kMaxTileSize = 1024; + const int kDefaultTileSize = 256; + + if (!m_OverrideTileSize) + m_TileSize = kDefaultTileSize; + // Make sure tilesize is in sane range. + if (m_TileSize < kMinTileSize) + m_TileSize = kMinTileSize; + if (m_TileSize > kMaxTileSize) + m_TileSize = kMaxTileSize; + } + } +#endif + } +} diff --git a/Assets/NavMeshComponents/Scripts/NavMeshSurface.cs.meta b/Assets/NavMeshComponents/Scripts/NavMeshSurface.cs.meta new file mode 100644 index 0000000..e7862cb --- /dev/null +++ b/Assets/NavMeshComponents/Scripts/NavMeshSurface.cs.meta @@ -0,0 +1,12 @@ +fileFormatVersion: 2 +guid: ac5b9bf9aa05ab44cbe4cedfac5c798a +timeCreated: 1477658803 +licenseType: Pro +MonoImporter: + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: e4f97225bcfb64760a1c81f460837f01, type: 3} + userData: + assetBundleName: + assetBundleVariant: