using UnityEditor;
using UnityEngine;
using System.Linq;
namespace Pathfinding {
using Pathfinding.Sync;
/// Editor for the component
[CustomEditor(typeof(NavmeshPrefab), true)]
[CanEditMultipleObjects]
public class NavmeshPrefabEditor : EditorBase {
protected override void OnEnable () {
base.OnEnable();
AstarPathEditor.LoadStyles();
EditorApplication.update += OnUpdate;
}
protected override void OnDisable () {
base.OnDisable();
EditorApplication.update -= OnUpdate;
}
void OnUpdate () {
}
static int pendingScanProgressId;
static int PendingScan (Promise[] pendingScanProgress, NavmeshPrefab[] pendingScanTargets) {
var progressId = UnityEditor.Progress.Start("Scanning Navmesh Prefab", "Scanning Navmesh Prefab", UnityEditor.Progress.Options.None, -1);
EditorApplication.CallbackFunction cb = null;
cb = () => {
if (UnityEditor.Progress.Exists(progressId)) {
bool allDone = true;
var avg = 0.0f;
for (int i = 0; i < pendingScanProgress.Length; i++) {
if (pendingScanTargets[i] == null) {
avg += 1.0f;
} else {
avg += pendingScanProgress[i].Progress;
if (pendingScanProgress[i].IsCompleted) {
var data = pendingScanProgress[i].Complete().data;
// Data can be null if some exception has been thrown during the scan
if (data != null) {
pendingScanTargets[i].SaveToFile(data);
}
pendingScanProgress[i].Dispose();
pendingScanTargets[i] = null;
pendingScanProgress[i] = default;
} else {
allDone = false;
}
}
}
avg /= pendingScanProgress.Length;
UnityEditor.Progress.Report(progressId, avg);
if (allDone) {
UnityEditor.Progress.Finish(progressId);
}
} else {
EditorApplication.update -= cb;
}
};
EditorApplication.update += cb;
return progressId;
}
protected override void Inspector () {
AstarPath.FindAstarPath();
Section("Shape");
BoundsField("bounds");
bool needsRounding = false;
RecastGraph graph = null;
if (AstarPath.active != null && AstarPath.active.data.recastGraph != null) {
graph = AstarPath.active.data.recastGraph;
}
bool isPrefab = EditorUtility.IsPersistent(this.target);
if (graph != null && !isPrefab) {
if (!graph.useTiles) {
EditorGUILayout.HelpBox("The recast graph in the scene doesn't use tiles. It needs to use tiles to be used with this component.", MessageType.Warning);
if (GUILayout.Button("Enable tiling on recast graph")) {
graph.useTiles = true;
}
}
var roundedTiles = this.targets.Cast().Select(target => {
var navmeshPrefab = target as NavmeshPrefab;
var bounds = navmeshPrefab.bounds;
var desiredBounds = NavmeshPrefab.SnapSizeToClosestTileMultiple(graph, bounds);
return new {
needsRounding = !Mathf.Approximately(bounds.extents.x, desiredBounds.extents.x) || !Mathf.Approximately(bounds.extents.z, desiredBounds.extents.z),
desiredBounds = desiredBounds
};
}).ToArray();
needsRounding = roundedTiles.Any(x => x.needsRounding);
if (needsRounding) {
EditorGUILayout.HelpBox("Bounds size is not a multiple of the recast graph's tile size (" + (graph.editorTileSize * graph.cellSize).ToString("0.0") + ").\nThe tile size is voxel size * tile size (voxels) (set in recast graph settings)", MessageType.Warning);
if (GUILayout.Button("Round to nearest multiple")) {
UnityEditor.Undo.RecordObjects(targets, "Snap to nearest tile multiple");
for (int i = 0; i < targets.Length; i++) {
(targets[i] as NavmeshPrefab).bounds = roundedTiles[i].desiredBounds;
EditorUtility.SetDirty(targets[i]);
}
}
}
if (GUILayout.Button("Snap position to nearest tile")) {
for (int i = 0; i < targets.Length; i++) {
var navmeshPrefab = targets[i] as NavmeshPrefab;
navmeshPrefab.SnapToClosestTileAlignment();
}
}
}
Section("Settings");
PropertyField("applyOnStart");
PropertyField("removeTilesWhenDisabled");
Section("Serialized Data");
EditorGUILayout.BeginHorizontal();
PropertyField("serializedNavmesh");
#if UNITY_2021_3_OR_NEWER
EditorGUI.showMixedValue = targets.Length > 1;
var target = this.target as NavmeshPrefab;
GUILayout.Label(EditorUtility.FormatBytes(target.serializedNavmesh != null ? target.serializedNavmesh.dataSize : 0), GUILayout.ExpandWidth(false));
EditorGUI.showMixedValue = false;
#endif
EditorGUILayout.EndHorizontal();
if (UnityEditor.Progress.Exists(pendingScanProgressId)) {
var r = EditorGUILayout.GetControlRect();
EditorGUI.ProgressBar(r, UnityEditor.Progress.GetProgress(pendingScanProgressId), "Scanning...");
Repaint();
} else {
EditorGUI.BeginDisabledGroup(needsRounding || isPrefab || graph == null);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Scan & Save")) {
var pendingScanProgress = new Promise[targets.Length];
var pendingScanTargets = new NavmeshPrefab[targets.Length];
for (int i = 0; i < targets.Length; i++) {
var navmeshPrefab = targets[i] as NavmeshPrefab;
pendingScanProgress[i] = navmeshPrefab.ScanAsync(graph);
// this.pendingScanProgress[i].Complete();
pendingScanTargets[i] = navmeshPrefab;
}
pendingScanProgressId = PendingScan(pendingScanProgress, pendingScanTargets);
}
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(isPrefab || graph == null);
if (GUILayout.Button("Edit graph", GUILayout.ExpandWidth(false))) {
Selection.activeGameObject = AstarPath.active.gameObject;
AstarPath.active.showGraphs = true;
}
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
if (isPrefab) {
EditorGUILayout.HelpBox("Open the prefab or add it to a scene to scan it.", MessageType.Info);
} else if (graph == null) {
EditorGUILayout.HelpBox("No recast graph was found in the scene. Add one if you want to scan this navmesh prefab.", MessageType.Info);
}
}
}
}
}