using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using UnityEngine; using UnityEngine.ProBuilder; using UnityEngine.ProBuilder.MeshOperations; using UnityEditor.SettingsManagement; #if !UNITY_2018_3_OR_NEWER using UnityEditor.SettingsManagement; #endif #if DEBUG_HANDLES using UnityEngine.Rendering; #endif namespace UnityEditor.ProBuilder { abstract class VertexManipulationTool { static Pref s_HandleOrientation = new Pref("editor.handleOrientation", HandleOrientation.World, SettingsScope.User); static Pref s_PivotPoint = new Pref("editor.pivotPoint", PivotPoint.Center, SettingsScope.User); [UserSetting(UserSettingsProvider.developerModeCategory, "PivotMode.Pivot", "Set the behavior of the \"Pivot\" handle mode when editing mesh elements.")] static Pref s_PivotModePivotEquivalent = new Pref("editor.pivotModePivotEquivalent", PivotPoint.ActiveElement, SettingsScope.User); [UserSetting(UserSettingsProvider.developerModeCategory, "Show Internal Pivot and Orientation")] static Pref s_ShowHandleSettingsInScene = new Pref("developer.showHandleSettingsInScene", false, SettingsScope.User); internal static PivotPoint pivotModePivotEquivalent { get { return s_PivotModePivotEquivalent; } set { s_PivotModePivotEquivalent.SetValue(value); } } internal static bool showHandleSettingsInScene { get { return s_ShowHandleSettingsInScene; } } // Store PivotRotation so that we can detect changes and update our handles appropriately static PivotRotation s_PivotRotation; /// /// Where the handle is positioned relative to the current selection. /// /// /// Relates to the UnityEditor.PivotMode enum, with additional options. /// public static PivotPoint pivotPoint { get { SyncPivotPoint(); return Tools.pivotMode == PivotMode.Pivot ? pivotModePivotEquivalent : PivotPoint.Center; } } static void SyncPivotPoint() { var unity = s_PivotPoint.value == PivotPoint.Center ? PivotMode.Center : PivotMode.Pivot; if (Tools.pivotMode != unity) { s_PivotPoint.SetValue(Tools.pivotMode == PivotMode.Center ? PivotPoint.Center : s_PivotModePivotEquivalent.value, true); MeshSelection.InvalidateElementSelection(); } } /// /// How the handle is rotated relative to the current selection. /// public static HandleOrientation handleOrientation { get { SyncPivotRotation(); return s_HandleOrientation; } set { s_HandleOrientation.SetValue(value, true); if (value != HandleOrientation.ActiveElement) Tools.pivotRotation = value == HandleOrientation.ActiveObject ? PivotRotation.Local : PivotRotation.Global; MeshSelection.InvalidateElementSelection(); var toolbar = typeof(EditorWindow).Assembly.GetType("UnityEditor.Toolbar"); var repaint = toolbar.GetMethod("RepaintToolbar", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public); repaint.Invoke(null, null); } } // Sync ProBuilder HandleOrientation to the current Tools.PivotRotation static void SyncPivotRotation() { if (s_PivotRotation != Tools.pivotRotation) { s_HandleOrientation.SetValue(Tools.pivotRotation == PivotRotation.Global ? HandleOrientation.World : HandleOrientation.ActiveObject); s_PivotRotation = Tools.pivotRotation; MeshSelection.InvalidateElementSelection(); return; } var value = s_HandleOrientation.value; var unity = value == HandleOrientation.ActiveObject ? PivotRotation.Local : PivotRotation.Global; if (value != HandleOrientation.ActiveElement) { if (unity != Tools.pivotRotation) { s_HandleOrientation.SetValue(Tools.pivotRotation == PivotRotation.Global ? HandleOrientation.World : HandleOrientation.ActiveObject, true); MeshSelection.InvalidateElementSelection(); } } } /// /// Called when vertex modifications are complete. /// public static event Action> afterMeshModification; /// /// Called immediately prior to beginning vertex modifications. The ProBuilderMesh will be in un-altered state at this point (meaning ProBuilderMesh.ToMesh and ProBuilderMesh.Refresh have been called, but not Optimize). /// public static event Action> beforeMeshModification; internal static Pref s_ExtrudeEdgesAsGroup = new Pref("editor.extrudeEdgesAsGroup", true); internal static Pref s_ExtrudeMethod = new Pref("editor.extrudeMethod", ExtrudeMethod.FaceNormal); Vector3 m_HandlePosition; Quaternion m_HandleRotation; Vector3 m_HandlePositionOrigin; Quaternion m_HandleRotationOrigin; bool m_IsEditing; float m_ProgridsSnapValue = .25f; bool m_SnapAxisConstraint = true; bool m_ProgridsSnapEnabled; static bool s_Initialized; static FieldInfo s_VertexDragging; static MethodInfo s_FindNearestVertex; static object[] s_FindNearestVertexArguments = new object[] { null, null, null }; internal IEnumerable elementSelection { get { return MeshSelection.elementSelection; } } protected static bool vertexDragging { get { Init(); return s_VertexDragging != null && (bool)s_VertexDragging.GetValue(null); } } protected bool isEditing { get { return m_IsEditing; } } protected Event currentEvent { get; private set; } protected Vector3 handlePositionOrigin { get { return m_HandlePositionOrigin; } } protected Quaternion handleRotationOriginInverse { get; private set; } protected Quaternion handleRotationOrigin { get { return m_HandleRotationOrigin; } } protected float progridsSnapValue { get { return m_ProgridsSnapValue; } } protected bool snapAxisConstraint { get { return m_SnapAxisConstraint; } } protected bool progridsSnapEnabled { get { return m_ProgridsSnapEnabled; } } protected bool relativeSnapEnabled { get { return currentEvent.control; } } static void Init() { if (s_Initialized) return; s_Initialized = true; s_VertexDragging = typeof(Tools).GetField("vertexDragging", BindingFlags.NonPublic | BindingFlags.Static); s_FindNearestVertex = typeof(HandleUtility).GetMethod("FindNearestVertex", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Instance); } internal abstract MeshAndElementSelection GetElementSelection(ProBuilderMesh mesh, PivotPoint pivot, HandleOrientation orientation); public void OnSceneGUI(Event evt) { // necessary because there is no callback on toolbar changes SyncPivotPoint(); SyncPivotRotation(); currentEvent = evt; if (evt.type == EventType.MouseUp || evt.type == EventType.Ignore) FinishEdit(); switch (ProBuilderEditor.selectMode) { case SelectMode.Face: case SelectMode.TextureFace: if (MeshSelection.selectedFaceCount < 1) return; break; case SelectMode.Edge: case SelectMode.TextureEdge: if (MeshSelection.selectedEdgeCount < 1) return; break; case SelectMode.Vertex: case SelectMode.TextureVertex: if (MeshSelection.selectedVertexCount < 1) return; break; } if (!m_IsEditing) { m_HandlePosition = MeshSelection.GetHandlePosition(); m_HandleRotation = MeshSelection.GetHandleRotation(); m_HandlePositionOrigin = m_HandlePosition; m_HandleRotationOrigin = m_HandleRotation; handleRotationOriginInverse = Quaternion.Inverse(m_HandleRotation); } DoTool(m_HandlePosition, m_HandleRotation); } protected abstract void DoTool(Vector3 handlePosition, Quaternion handleRotation); protected virtual void OnToolEngaged() {} protected virtual void OnToolDisengaged() {} protected void BeginEdit(string undoMessage) { if (m_IsEditing) return; // Disable iterative lightmapping Lightmapping.PushGIWorkflowMode(); var selection = MeshSelection.topInternal.ToArray(); UndoUtility.RegisterCompleteObjectUndo(selection, string.IsNullOrEmpty(undoMessage) ? "Modify Vertices" : undoMessage); if (beforeMeshModification != null) beforeMeshModification(selection); if (currentEvent.shift) Extrude(); m_IsEditing = true; m_ProgridsSnapEnabled = ProGridsInterface.SnapEnabled(); m_ProgridsSnapValue = ProGridsInterface.SnapValue(); m_SnapAxisConstraint = ProGridsInterface.UseAxisConstraints(); foreach (var mesh in selection) { mesh.ToMesh(); mesh.Refresh(); } OnToolEngaged(); } protected void FinishEdit() { if (!m_IsEditing) return; Lightmapping.PopGIWorkflowMode(); OnToolDisengaged(); var selection = MeshSelection.topInternal.ToArray(); foreach (var mesh in selection) { mesh.ToMesh(); mesh.Refresh(); mesh.Optimize(); } ProBuilderEditor.Refresh(); if (afterMeshModification != null) afterMeshModification(selection); m_IsEditing = false; } static void Extrude() { int ef = 0; var selection = MeshSelection.topInternal; var selectMode = ProBuilderEditor.selectMode; foreach (var mesh in selection) { switch (selectMode) { case SelectMode.Edge: if (mesh.selectedFaceCount > 0) goto default; Edge[] newEdges = mesh.Extrude(mesh.selectedEdges, 0.0001f, s_ExtrudeEdgesAsGroup, ProBuilderEditor.s_AllowNonManifoldActions); if (newEdges != null) { ef += newEdges.Length; mesh.SetSelectedEdges(newEdges); } break; default: int len = mesh.selectedFacesInternal.Length; if (len > 0) { mesh.Extrude(mesh.selectedFacesInternal, s_ExtrudeMethod, 0.0001f); mesh.SetSelectedFaces(mesh.selectedFacesInternal); ef += len; } break; } mesh.ToMesh(); mesh.Refresh(); } if (ef > 0) { EditorUtility.ShowNotification("Extrude"); ProBuilderEditor.Refresh(); } } /// /// Find the nearest vertex among all visible objects. /// /// /// /// protected static bool FindNearestVertex(Vector2 mousePosition, out Vector3 vertex) { s_FindNearestVertexArguments[0] = mousePosition; if (s_FindNearestVertex == null) s_FindNearestVertex = typeof(HandleUtility).GetMethod("findNearestVertex", BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Instance); object result = s_FindNearestVertex.Invoke(null, s_FindNearestVertexArguments); vertex = (bool)result ? (Vector3)s_FindNearestVertexArguments[2] : Vector3.zero; return (bool)result; } } }