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;
}
}
}