using System.Collections.Generic; using UnityEditor; using UnityEngine; namespace MTE { internal class SmoothHeightEditor : IEditor { public int Id { get; } = 3; public bool Enabled { get; set; } = true; public string Name { get; } = "SmoothHeightEditor"; public Texture Icon { get; } = EditorGUIUtility.IconContent("TerrainInspector.TerrainToolSmoothHeight").image; public bool WantMouseMove { get; } = false; public bool WillEditMesh { get; } = true; #region Parameters #region Constant // default const float DefaultBrushSize = 4; const float DefaultSpeed = 0.5f; // min/max const float MinBrushSize = 0.1f; const float MaxBrushSize = 10f; const float MinSpeed = 0.01f; const float MaxSpeed = 1f; #endregion private float brushSize; private float speed; /// /// Brush size (unit: meter) /// public float BrushSize { get { return brushSize; } set { value = Mathf.Clamp(value, MinBrushSize, MaxBrushSize); if (!MathEx.AmostEqual(value, brushSize)) { brushSize = value; EditorPrefs.SetFloat("MTE_SmoothHeightEditor.brushSize", value); } } } private float BrushSizeInU3D { get { return BrushSize * Settings.BrushUnit; } } /// /// Speed /// public float Speed { get { return speed; } set { value = Mathf.Clamp(value, MinSpeed, MaxSpeed); if (!MathEx.AmostEqual(value, speed)) { speed = value; EditorPrefs.SetFloat("MTE_SmoothHeightEditor.speed", value); } } } public int JumpUpdateFrameCount = 5; #endregion public SmoothHeightEditor() { MTEContext.EnableEvent += (sender, args) => { if (MTEContext.editor == this) { LoadSavedParamter(); } }; MTEContext.EditTypeChangedEvent += (sender, args) => { if (MTEContext.editor == this) { LoadSavedParamter(); } }; brushSize = DefaultBrushSize; speed = DefaultSpeed; } private void LoadSavedParamter() { // Load parameters from the EditorPrefs brushSize = EditorPrefs.GetFloat("MTE_SmoothHeightEditor.brushSize", DefaultBrushSize); speed = EditorPrefs.GetFloat("MTE_SmoothHeightEditor.speed", DefaultSpeed); } public HashSet DefineHotkeys() { return new HashSet { new Hotkey(this, KeyCode.Minus, () => { Speed -= 0.01f; MTEEditorWindow.Instance.Repaint(); }), new Hotkey(this, KeyCode.Equals, () => { Speed += 0.01f; MTEEditorWindow.Instance.Repaint(); }), new Hotkey(this, KeyCode.LeftBracket, () => { BrushSize -= 1; MTEEditorWindow.Instance.Repaint(); }), new Hotkey(this, KeyCode.RightBracket, () => { BrushSize += 1; MTEEditorWindow.Instance.Repaint(); }) }; } public string Header { get { return StringTable.Get(C.SmoothHeight_Header); } } public string Description { get { return StringTable.Get(C.SmoothHeight_Description); } } public void DoArgsGUI() { if (!Settings.CompactGUI) { GUILayout.Label(StringTable.Get(C.Settings), MTEStyles.SubHeader); } BrushSize = EditorGUILayoutEx.Slider(StringTable.Get(C.Size), "-", "+", BrushSize, MinBrushSize, MaxBrushSize); Speed = EditorGUILayoutEx.Slider(StringTable.Get(C.Speed), "[", "]", Speed, MinSpeed, MaxSpeed); } private readonly List modifyGroups = new List(4); public void OnSceneGUI() { var e = Event.current; if (!(EditorWindow.mouseOverWindow is SceneView)) { return; } // do nothing when mouse middle/right button, control/alt key is pressed if (e.button != 0 || e.control || e.alt) return; HandleUtility.AddDefaultControl(0); RaycastHit raycastHit; Ray ray = HandleUtility.GUIPointToWorldRay(e.mousePosition); //Debug.Log(string.Format("mouse at ({0}, {1})", e.mousePosition.x, e.mousePosition.y)); if (Physics.Raycast(ray, out raycastHit, Mathf.Infinity, 1 << MTEContext.TargetLayer//only hit target layer )) { //check tag if (!raycastHit.transform.CompareTag(MTEContext.TargetTag)) { return; } if (Settings.ShowBrushRect) { Utility.ShowBrushRect(raycastHit.point, BrushSizeInU3D); } // collect modifiy group modifyGroups.Clear(); foreach (var target in MTEContext.Targets) { var meshFilter = target.GetComponent(); var meshCollider = target.GetComponent(); var mesh = meshFilter.sharedMesh; var hitPointLocal = target.transform.InverseTransformPoint(raycastHit.point); List vIndex; VertexMap.GetAffectedVertex(target, hitPointLocal, BrushSizeInU3D, out vIndex); if (Settings.ShowAffectedVertex) { Utility.ShowAffectedVertices(target, mesh, vIndex); } if (e.type == EventType.MouseDown || e.type == EventType.MouseDrag) { if (vIndex.Count != 0) { modifyGroups.Add(new MeshModifyGroup(target, mesh, meshCollider, vIndex, vDistance: null)); } } } //record undo operation for targets that to be modified if (e.type == EventType.MouseDown) { Utility.Record("Smooth Height", raycastHit.point, this.BrushSizeInU3D); } // execute the modification if (modifyGroups.Count != 0) { for (int i = 0; i < modifyGroups.Count; i++) { var modifyGroup = modifyGroups[i]; SmoothHeight(modifyGroup.gameObject, modifyGroup.meshCollider, modifyGroup.vIndex); } } if (e.type == EventType.MouseUp) { MTEEditorWindow.Instance.UpdateDirtyMeshCollidersImmediately(); MTEEditorWindow.Instance.HandleMeshSave(); } } SceneView.RepaintAll(); } /// /// Smooth heights of vertexes /// public void SmoothHeight(GameObject obj, MeshCollider meshCollider, List vIndex) { float currentBrushSize = BrushSizeInU3D; var mesh = obj.GetComponent().sharedMesh; Transform transform = obj.transform; var copied_vertexes = mesh.vertices; for (var i = 0; i < vIndex.Count; ++i) { var index = vIndex[i]; var vertex = copied_vertexes[index]; var vertex_world = transform.TransformPoint(vertex); var nearbyAverage = VertexMap.GetNearByVerticesAverageHeight(vertex_world, currentBrushSize); var originalHeight = vertex.y; var average = 0.5f * nearbyAverage + 0.5f * originalHeight; var detla = average - originalHeight; var smoothRise = detla * Speed; copied_vertexes[index].y += smoothRise; } mesh.vertices = copied_vertexes; // rebuild vertex map, because smooth needs the real heights of each vertex VertexMap.Rebuild(obj); MTEEditorWindow.Instance.SetMeshDirty(obj); MTEEditorWindow.Instance.SetMeshColliderDirty(meshCollider, mesh.vertexCount); } } }