using UnityEngine; using System.Linq; using System.Collections.Generic; using System.Collections.ObjectModel; using UnityEngine.ProBuilder; using UnityEngine.ProBuilder.MeshOperations; namespace UnityEditor.ProBuilder { /// /// Helper functions for working with Unity object selection and ProBuilder mesh attribute selections. /// [InitializeOnLoad] public static class MeshSelection { static List s_TopSelection = new List(); static ProBuilderMesh s_ActiveMesh; static List s_ElementSelection = new List(); static bool s_TotalElementCountCacheIsDirty = true; static bool s_SelectedElementGroupsDirty = true; static Bounds s_SelectionBounds = new Bounds(); /// /// An axis-aligned bounding box encompassing the selected elements. /// public static Bounds bounds { get { return s_SelectionBounds; } } static int s_TotalVertexCount; static int s_TotalFaceCount; static int s_TotalEdgeCount; static int s_TotalCommonVertexCount; static int s_TotalVertexCountCompiled; static int s_TotalTriangleCountCompiled; /// /// How many ProBuilderMesh components are currently selected. Corresponds to the length of Top. /// public static int selectedObjectCount { get; private set; } /// /// The sum of all selected ProBuilderMesh selected vertex counts. /// /// public static int selectedVertexCount { get; private set; } /// /// The sum of all selected ProBuilderMesh selected shared vertex counts. /// /// public static int selectedSharedVertexCount { get; private set; } /// /// The sum of all selected ProBuilderMesh selected face counts. /// public static int selectedFaceCount { get; private set; } /// /// The sum of all selected ProBuilderMesh selected edge counts. /// public static int selectedEdgeCount { get; private set; } // per-object selected element maxes internal static int selectedFaceCountObjectMax { get; private set; } internal static int selectedEdgeCountObjectMax { get; private set; } internal static int selectedVertexCountObjectMax { get; private set; } internal static int selectedSharedVertexCountObjectMax { get; private set; } // Faces that need to be refreshed when moving or modifying the actual selection internal static Dictionary> selectedFacesInEditZone { get; private set; } internal static void InvalidateElementSelection() { s_SelectedElementGroupsDirty = true; } internal static IEnumerable elementSelection { get { RecalculateSelectedElementGroups(); return s_ElementSelection; } } static MeshSelection() { Selection.selectionChanged += OnObjectSelectionChanged; ProBuilderMesh.elementSelectionChanged += ElementSelectionChanged; EditorMeshUtility.meshOptimized += (x, y) => { s_TotalElementCountCacheIsDirty = true; }; OnObjectSelectionChanged(); } /// /// Returns the active selected mesh. /// public static ProBuilderMesh activeMesh { get { return s_ActiveMesh; } } internal static Face activeFace { get { return activeMesh != null ? activeMesh.selectedFacesInternal.LastOrDefault() : null; } } /// /// Receive notifications when the object selection changes. /// public static event System.Action objectSelectionChanged; internal static void OnObjectSelectionChanged() { // GameObjects returns both parent and child when both are selected, where transforms only returns the top-most // transform. s_TopSelection.Clear(); s_ElementSelection.Clear(); s_ActiveMesh = null; var gameObjects = Selection.gameObjects; for (int i = 0, c = gameObjects.Length; i < c; i++) { var mesh = gameObjects[i].GetComponent(); if (mesh != null) { if (gameObjects[i] == Selection.activeGameObject) s_ActiveMesh = mesh; s_TopSelection.Add(mesh); } } selectedObjectCount = s_TopSelection.Count; OnComponentSelectionChanged(); if (objectSelectionChanged != null) objectSelectionChanged(); } internal static void OnComponentSelectionChanged() { s_TotalElementCountCacheIsDirty = true; s_SelectedElementGroupsDirty = true; selectedVertexCount = 0; selectedFaceCount = 0; selectedEdgeCount = 0; selectedSharedVertexCount = 0; selectedFaceCountObjectMax = 0; selectedVertexCountObjectMax = 0; selectedSharedVertexCountObjectMax = 0; selectedEdgeCountObjectMax = 0; RecalculateSelectedComponentCounts(); RecalculateFacesInEditableArea(); RecalculateSelectionBounds(); } static void ElementSelectionChanged(ProBuilderMesh mesh) { InvalidateElementSelection(); } internal static void RecalculateSelectedElementGroups() { if (!s_SelectedElementGroupsDirty) return; s_SelectedElementGroupsDirty = false; s_ElementSelection.Clear(); var activeTool = ProBuilderEditor.activeTool; if (activeTool != null) { foreach (var mesh in s_TopSelection) { s_ElementSelection.Add(activeTool.GetElementSelection(mesh, VertexManipulationTool.pivotPoint, VertexManipulationTool.handleOrientation)); } } } internal static void RecalculateSelectedComponentCounts() { for (var i = 0; i < topInternal.Count; i++) { var mesh = topInternal[i]; selectedFaceCount += mesh.selectedFaceCount; selectedEdgeCount += mesh.selectedEdgeCount; selectedVertexCount += mesh.selectedIndexesInternal.Length; selectedSharedVertexCount += mesh.selectedSharedVerticesCount; selectedVertexCountObjectMax = System.Math.Max(selectedVertexCountObjectMax, mesh.selectedIndexesInternal.Length); selectedSharedVertexCountObjectMax = System.Math.Max(selectedSharedVertexCountObjectMax, mesh.selectedSharedVerticesCount); selectedFaceCountObjectMax = System.Math.Max(selectedFaceCountObjectMax, mesh.selectedFaceCount); selectedEdgeCountObjectMax = System.Math.Max(selectedEdgeCountObjectMax, mesh.selectedEdgeCount); } } internal static void RecalculateSelectionBounds() { s_SelectionBounds = new Bounds(); var boundsInitialized = false; for (int i = 0, c = topInternal.Count; i < c; i++) { var mesh = topInternal[i]; // Undo causes this state if (mesh == null) return; if (!boundsInitialized && mesh.selectedVertexCount > 0) { boundsInitialized = true; s_SelectionBounds = new Bounds(mesh.transform.TransformPoint(mesh.positionsInternal[mesh.selectedIndexesInternal[0]]), Vector3.zero); } if (mesh.selectedVertexCount > 0) { var shared = mesh.sharedVerticesInternal; foreach (var sharedVertex in mesh.selectedSharedVertices) s_SelectionBounds.Encapsulate(mesh.transform.TransformPoint(mesh.positionsInternal[shared[sharedVertex][0]])); } } } internal static void RecalculateFacesInEditableArea() { if (selectedFacesInEditZone != null) selectedFacesInEditZone.Clear(); else selectedFacesInEditZone = new Dictionary>(); foreach (var mesh in topInternal) { selectedFacesInEditZone.Add(mesh, ElementSelection.GetNeighborFaces(mesh, mesh.selectedIndexesInternal)); } } /// /// Get all selected ProBuilderMesh components. Corresponds to x.GetComponent().Where(y => y != null);]]>. /// /// An array of the currently selected ProBuilderMesh components. Does not include children of selected objects. public static IEnumerable top { get { return new ReadOnlyCollection(s_TopSelection); } } internal static List topInternal { get { return s_TopSelection; } } /// /// Get all selected ProBuilderMesh components, including those in children of selected objects. /// /// All selected ProBuilderMesh components, including those in children of selected objects. public static IEnumerable deep { get { return Selection.gameObjects.SelectMany(x => x.GetComponentsInChildren()); } } internal static bool Contains(ProBuilderMesh mesh) { return s_TopSelection.Contains(mesh); } /// /// Get the number of all selected vertices across the selected ProBuilder meshes. /// /// /// This is the ProBuilderMesh.vertexCount, not UnityEngine.Mesh.vertexCount. To get the optimized mesh vertex count, /// see `totalVertexCountCompiled` for the vertex count as is rendered in the scene. /// public static int totalVertexCount { get { RebuildElementCounts(); return s_TotalVertexCount; } } /// /// Get the number of all selected vertices across the selected ProBuilder meshes, excluding coincident duplicates. /// public static int totalCommonVertexCount { get { RebuildElementCounts(); return s_TotalCommonVertexCount; } } internal static int totalVertexCountOptimized { get { RebuildElementCounts(); return s_TotalVertexCountCompiled; } } /// /// Sum of all selected ProBuilderMesh object faceCount properties. /// public static int totalFaceCount { get { RebuildElementCounts(); return s_TotalFaceCount; } } /// /// Sum of all selected ProBuilderMesh object edgeCount properties. /// public static int totalEdgeCount { get { RebuildElementCounts(); return s_TotalEdgeCount; } } /// /// Get the sum of all selected ProBuilder compiled mesh triangle counts (3 indexes make up a triangle, or 4 indexes if topology is quad). /// public static int totalTriangleCountCompiled { get { RebuildElementCounts(); return s_TotalTriangleCountCompiled; } } static void RebuildElementCounts() { if (!s_TotalElementCountCacheIsDirty) return; try { s_TotalVertexCount = topInternal.Sum(x => x.vertexCount); s_TotalFaceCount = topInternal.Sum(x => x.faceCount); s_TotalEdgeCount = topInternal.Sum(x => x.edgeCount); s_TotalCommonVertexCount = topInternal.Sum(x => x.sharedVerticesInternal.Length); s_TotalVertexCountCompiled = topInternal.Sum(x => x.mesh == null ? 0 : x.mesh.vertexCount); s_TotalTriangleCountCompiled = topInternal.Sum(x => (int)UnityEngine.ProBuilder.MeshUtility.GetPrimitiveCount(x.mesh)); s_TotalElementCountCacheIsDirty = false; } catch { // expected when UndoRedo is called } } internal static void AddToSelection(GameObject t) { if (t == null || Selection.objects.Contains(t)) return; int len = Selection.objects.Length; Object[] temp = new Object[len + 1]; for (int i = 0; i < len; i++) temp[i] = Selection.objects[i]; temp[len] = t; Selection.activeObject = t; Selection.objects = temp; } internal static void RemoveFromSelection(GameObject t) { int ind = System.Array.IndexOf(Selection.objects, t); if (ind < 0) return; Object[] temp = new Object[Selection.objects.Length - 1]; for (int i = 1; i < temp.Length; i++) { if (i != ind) temp[i] = Selection.objects[i]; } Selection.objects = temp; if (Selection.activeGameObject == t) Selection.activeObject = temp.FirstOrDefault(); } internal static void SetSelection(IList newSelection) { UndoUtility.RecordSelection(topInternal.ToArray(), "Change Selection"); ClearElementAndObjectSelection(); // if the previous tool was set to none, use Tool.Move if (Tools.current == Tool.None) Tools.current = Tool.Move; var newCount = newSelection != null ? newSelection.Count : 0; if (newCount > 0) { Selection.activeTransform = newSelection[newCount - 1].transform; Selection.objects = newSelection.ToArray(); } else { Selection.activeTransform = null; } } internal static void SetSelection(GameObject go) { UndoUtility.RecordSelection(topInternal.ToArray(), "Change Selection"); ClearElementAndObjectSelection(); AddToSelection(go); } /// /// Clears all selected mesh attributes in the current selection. This means triangles, faces, and edges, but not objects. /// public static void ClearElementSelection() { if (ProBuilderEditor.instance) ProBuilderEditor.instance.ClearElementSelection(); s_TotalElementCountCacheIsDirty = true; if (objectSelectionChanged != null) objectSelectionChanged(); } /// /// Clear both the Selection.objects and ProBuilder mesh attribute selections. /// public static void ClearElementAndObjectSelection() { if (ProBuilderEditor.instance) ProBuilderEditor.instance.ClearElementSelection(); Selection.objects = new Object[0]; } internal static Vector3 GetHandlePosition() { var active = GetActiveSelectionGroup(); return active != null && active.elementGroups.Count > 0 ? active.elementGroups.Last().position : Vector3.zero; } internal static Quaternion GetHandleRotation() { var active = GetActiveSelectionGroup(); return active != null && active.elementGroups.Count > 0 ? active.elementGroups.Last().rotation : Quaternion.identity; } internal static MeshAndElementSelection GetActiveSelectionGroup() { foreach (var pair in elementSelection) { if (pair.mesh == s_ActiveMesh) return pair; } return null; } } }