Added player character and step in/out of dialogue in scene 101
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73c672377c494b42b2cf29016e7f37eb
|
||||
@@ -0,0 +1,5 @@
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.AddOns.Editor")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Tests")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Editor.Tests")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.MenuItems")]
|
||||
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Unity.ProBuilder.Debug.Editor")]
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6624e07ef7bd4f379420666341dd539
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,576 @@
|
||||
#pragma warning disable 0414
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.ProBuilder.MeshOperations;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom editor for pb_UV type.
|
||||
/// </summary>
|
||||
static class AutoUVEditor
|
||||
{
|
||||
#region MEMBERS
|
||||
|
||||
static ProBuilderEditor editor
|
||||
{
|
||||
get { return ProBuilderEditor.instance; }
|
||||
}
|
||||
|
||||
static AutoUnwrapSettings s_AutoUVSettings = AutoUnwrapSettings.tile;
|
||||
static int textureGroup = -1;
|
||||
static List<AutoUnwrapSettings> s_AutoUVSettingsInSelection = new List<AutoUnwrapSettings>();
|
||||
static Dictionary<string, bool> s_AutoUVSettingsDiff = new Dictionary<string, bool>()
|
||||
{
|
||||
{ "projectionAxis", false },
|
||||
{ "useWorldSpace", false },
|
||||
{ "flipU", false },
|
||||
{ "flipV", false },
|
||||
{ "swapUV", false },
|
||||
{ "fill", false },
|
||||
{ "scalex", false },
|
||||
{ "scaley", false },
|
||||
{ "offsetx", false },
|
||||
{ "offsety", false },
|
||||
{ "rotation", false },
|
||||
{ "anchor", false },
|
||||
{ "manualUV", false },
|
||||
{ "textureGroup", false }
|
||||
};
|
||||
|
||||
public enum Axis2D
|
||||
{
|
||||
XY,
|
||||
X,
|
||||
Y
|
||||
}
|
||||
|
||||
static Vector2 s_ScrollPosition;
|
||||
|
||||
#endregion
|
||||
|
||||
#region ONGUI
|
||||
|
||||
static readonly GUIContent gc_FillMode = EditorGUIUtility.TrTextContent("Fill Mode", "How the projected UVs are tiled or stretched.");
|
||||
static readonly GUIContent gc_Anchor = EditorGUIUtility.TrTextContent("Anchor", "After the initial projection into 2D space, UVs will be translated to the anchor position. Next, the offset and rotation are applied, followed by the various other settings.");
|
||||
static readonly GUIContent gc_Offset = EditorGUIUtility.TrTextContent("Offset", "Translation applied to UVs after projection into 2D space.");
|
||||
static readonly GUIContent gc_Rotation = EditorGUIUtility.TrTextContent("Rotation", "Rotation around the center of face UV bounds.");
|
||||
static readonly GUIContent gc_Tiling = EditorGUIUtility.TrTextContent("Tiling", "Scaling applied to UVs after projection into 2D space");
|
||||
static readonly GUIContent gc_WorldSpace = EditorGUIUtility.TrTextContent("World Space", "When enabled UVs are projected using their world space coordinates.");
|
||||
static readonly GUIContent gc_FlipU = EditorGUIUtility.TrTextContent("Flip U", "Mirror UV coordinates horizontally.");
|
||||
static readonly GUIContent gc_FlipV = EditorGUIUtility.TrTextContent("Flip V", "Mirror UV coordinates vertically.");
|
||||
static readonly GUIContent gc_SwapUV = EditorGUIUtility.TrTextContent("Swap U/V", "Flips the U and V components. UV becomes VU.");
|
||||
static readonly GUIContent gc_TextureGroup = EditorGUIUtility.TrTextContent("Texture Group", "Faces in a texture group will be UV mapped as a group, just as though you had selected these faces and used the \"Planar Project\" action");
|
||||
static readonly GUIContent gc_GroupSelected = EditorGUIUtility.TrTextContent("Group Selected Faces", "This sets all selected faces to share a texture group. What that means is that the UVs on these faces will all be projected as though they are a single plane. Ideal candidates for texture groups are floors with multiple faces, walls with edge loops, flat surfaces, etc.");
|
||||
static readonly GUIContent gc_BreakSelected = EditorGUIUtility.TrTextContent("Break Selected Groups", "This resets all the selected face Texture Groups.");
|
||||
static readonly GUIContent gc_SelectGroup = EditorGUIUtility.TrTextContent("Select Texture Group", "Selects all faces contained in this texture group.");
|
||||
static readonly GUIContent gc_Reset = EditorGUIUtility.TrTextContent("Reset UVs", "Reset UV projection parameters.");
|
||||
|
||||
public static bool OnGUI(ProBuilderMesh[] selection, float width)
|
||||
{
|
||||
UpdateDiffDictionary(selection);
|
||||
|
||||
s_ScrollPosition = EditorGUILayout.BeginScrollView(s_ScrollPosition);
|
||||
float tempFloat = 0f;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
// Set Tile mode
|
||||
GUILayout.Label("Tiling & Alignment", EditorStyles.boldLabel);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
EditorGUI.showMixedValue = s_AutoUVSettingsDiff["fill"];
|
||||
GUILayout.Label(gc_FillMode, GUILayout.MaxWidth(80), GUILayout.MinWidth(80));
|
||||
EditorGUI.BeginChangeCheck();
|
||||
s_AutoUVSettings.fill = (AutoUnwrapSettings.Fill)EditorGUILayout.EnumPopup(s_AutoUVSettings.fill);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SetFill(s_AutoUVSettings.fill, selection);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
bool enabled = GUI.enabled;
|
||||
GUI.enabled = !s_AutoUVSettings.useWorldSpace;
|
||||
EditorGUI.showMixedValue = s_AutoUVSettingsDiff["anchor"];
|
||||
EditorGUI.BeginChangeCheck();
|
||||
GUILayout.Label(gc_Anchor, GUILayout.MaxWidth(80), GUILayout.MinWidth(80));
|
||||
s_AutoUVSettings.anchor = (AutoUnwrapSettings.Anchor)EditorGUILayout.EnumPopup(s_AutoUVSettings.anchor);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SetAnchor(s_AutoUVSettings.anchor, selection);
|
||||
GUI.enabled = enabled;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUI.backgroundColor = PreferenceKeys.proBuilderLightGray;
|
||||
UI.EditorGUIUtility.DrawSeparator(1);
|
||||
GUI.backgroundColor = Color.white;
|
||||
|
||||
GUILayout.Label("Transform", EditorStyles.boldLabel);
|
||||
|
||||
//Offset
|
||||
EditorGUI.showMixedValue = s_AutoUVSettingsDiff["offsetx"] || s_AutoUVSettingsDiff["offsety"];
|
||||
var tempVec2 = s_AutoUVSettings.offset;
|
||||
UnityEngine.GUI.SetNextControlName("offset");
|
||||
s_AutoUVSettings.offset = EditorGUILayout.Vector2Field(gc_Offset, s_AutoUVSettings.offset, GUILayout.MaxWidth(width));
|
||||
if (tempVec2.x != s_AutoUVSettings.offset.x) { SetOffset(s_AutoUVSettings.offset, Axis2D.X, selection); }
|
||||
if (tempVec2.y != s_AutoUVSettings.offset.y) { SetOffset(s_AutoUVSettings.offset, Axis2D.Y, selection); }
|
||||
|
||||
// Rotation
|
||||
tempFloat = s_AutoUVSettings.rotation;
|
||||
EditorGUI.showMixedValue = s_AutoUVSettingsDiff["rotation"];
|
||||
GUILayout.Label(gc_Rotation, GUILayout.MaxWidth(width - 64));
|
||||
UnityEngine.GUI.SetNextControlName("rotation");
|
||||
EditorGUI.BeginChangeCheck();
|
||||
tempFloat = EditorGUILayout.Slider(tempFloat, 0f, 360f, GUILayout.MaxWidth(width));
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SetRotation(tempFloat, selection);
|
||||
|
||||
// Scale
|
||||
EditorGUI.showMixedValue = s_AutoUVSettingsDiff["scalex"] || s_AutoUVSettingsDiff["scaley"];
|
||||
tempVec2 = s_AutoUVSettings.scale;
|
||||
GUI.SetNextControlName("scale");
|
||||
EditorGUI.BeginChangeCheck();
|
||||
s_AutoUVSettings.scale = EditorGUILayout.Vector2Field(gc_Tiling, s_AutoUVSettings.scale, GUILayout.MaxWidth(width));
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (tempVec2.x != s_AutoUVSettings.scale.x) { SetScale(s_AutoUVSettings.scale, Axis2D.X, selection); }
|
||||
if (tempVec2.y != s_AutoUVSettings.scale.y) { SetScale(s_AutoUVSettings.scale, Axis2D.Y, selection); }
|
||||
}
|
||||
|
||||
// Draw tiling shortcuts
|
||||
GUILayout.BeginHorizontal();
|
||||
if (GUILayout.Button(".5", EditorStyles.miniButtonLeft)) SetScale(Vector2.one * 2f, Axis2D.XY, selection);
|
||||
if (GUILayout.Button("1", EditorStyles.miniButtonMid)) SetScale(Vector2.one, Axis2D.XY, selection);
|
||||
if (GUILayout.Button("2", EditorStyles.miniButtonMid)) SetScale(Vector2.one * .5f, Axis2D.XY, selection);
|
||||
if (GUILayout.Button("4", EditorStyles.miniButtonMid)) SetScale(Vector2.one * .25f, Axis2D.XY, selection);
|
||||
if (GUILayout.Button("8", EditorStyles.miniButtonMid)) SetScale(Vector2.one * .125f, Axis2D.XY, selection);
|
||||
if (GUILayout.Button("16", EditorStyles.miniButtonRight)) SetScale(Vector2.one * .0625f, Axis2D.XY, selection);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(4);
|
||||
|
||||
UnityEngine.GUI.backgroundColor = PreferenceKeys.proBuilderLightGray;
|
||||
UI.EditorGUIUtility.DrawSeparator(1);
|
||||
UnityEngine.GUI.backgroundColor = Color.white;
|
||||
|
||||
// Special
|
||||
GUILayout.Label("Special", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.showMixedValue = s_AutoUVSettingsDiff["useWorldSpace"];
|
||||
EditorGUI.BeginChangeCheck();
|
||||
s_AutoUVSettings.useWorldSpace = EditorGUILayout.Toggle(gc_WorldSpace, s_AutoUVSettings.useWorldSpace);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SetUseWorldSpace(s_AutoUVSettings.useWorldSpace, selection);
|
||||
|
||||
GUI.backgroundColor = PreferenceKeys.proBuilderLightGray;
|
||||
UI.EditorGUIUtility.DrawSeparator(1);
|
||||
GUI.backgroundColor = Color.white;
|
||||
|
||||
|
||||
// Flip U
|
||||
EditorGUI.showMixedValue = s_AutoUVSettingsDiff["flipU"];
|
||||
EditorGUI.BeginChangeCheck();
|
||||
s_AutoUVSettings.flipU = EditorGUILayout.Toggle(gc_FlipU, s_AutoUVSettings.flipU);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SetFlipU(s_AutoUVSettings.flipU, selection);
|
||||
|
||||
// Flip V
|
||||
EditorGUI.showMixedValue = s_AutoUVSettingsDiff["flipV"];
|
||||
EditorGUI.BeginChangeCheck();
|
||||
s_AutoUVSettings.flipV = EditorGUILayout.Toggle(gc_FlipV, s_AutoUVSettings.flipV);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SetFlipV(s_AutoUVSettings.flipV, selection);
|
||||
|
||||
EditorGUI.showMixedValue = s_AutoUVSettingsDiff["swapUV"];
|
||||
EditorGUI.BeginChangeCheck();
|
||||
s_AutoUVSettings.swapUV = EditorGUILayout.Toggle(gc_SwapUV, s_AutoUVSettings.swapUV);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SetSwapUV(s_AutoUVSettings.swapUV, selection);
|
||||
|
||||
// Texture Groups
|
||||
GUILayout.Label("Texture Groups", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
EditorGUI.showMixedValue = s_AutoUVSettingsDiff["textureGroup"];
|
||||
|
||||
GUI.SetNextControlName("textureGroup");
|
||||
textureGroup = UI.EditorGUIUtility.IntFieldConstrained(gc_TextureGroup, textureGroup, (int)width);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
SetTextureGroup(selection, textureGroup);
|
||||
|
||||
foreach (var kvp in MeshSelection.selectedFacesInEditZone)
|
||||
kvp.Key.RefreshUV(kvp.Value);
|
||||
|
||||
SceneView.RepaintAll();
|
||||
|
||||
s_AutoUVSettingsDiff["textureGroup"] = false;
|
||||
}
|
||||
|
||||
if (GUILayout.Button(gc_GroupSelected))
|
||||
{
|
||||
for (int i = 0; i < selection.Length; i++)
|
||||
TextureGroupSelectedFaces(selection[i]);
|
||||
|
||||
ProBuilderEditor.Refresh();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(gc_BreakSelected))
|
||||
{
|
||||
SetTextureGroup(selection, -1);
|
||||
|
||||
foreach (var kvp in MeshSelection.selectedFacesInEditZone)
|
||||
{
|
||||
kvp.Key.ToMesh();
|
||||
kvp.Key.Refresh();
|
||||
kvp.Key.Optimize();
|
||||
}
|
||||
|
||||
SceneView.RepaintAll();
|
||||
|
||||
s_AutoUVSettingsDiff["textureGroup"] = false;
|
||||
|
||||
ProBuilderEditor.Refresh();
|
||||
}
|
||||
|
||||
/* Select all in current texture group */
|
||||
if (GUILayout.Button(gc_SelectGroup))
|
||||
{
|
||||
for (int i = 0; i < selection.Length; i++)
|
||||
selection[i].SetSelectedFaces(System.Array.FindAll(selection[i].facesInternal, x => x.textureGroup == textureGroup));
|
||||
|
||||
ProBuilderEditor.Refresh();
|
||||
}
|
||||
|
||||
if (GUILayout.Button(gc_Reset))
|
||||
{
|
||||
UndoUtility.RecordSelection(selection, "Reset UVs");
|
||||
|
||||
for (int i = 0; i < selection.Length; i++)
|
||||
{
|
||||
foreach (Face face in selection[i].GetSelectedFaces())
|
||||
{
|
||||
face.uv = AutoUnwrapSettings.tile;
|
||||
face.textureGroup = -1;
|
||||
face.elementGroup = -1;
|
||||
}
|
||||
|
||||
UVEditing.SplitUVs(selection[i], selection[i].GetSelectedFaces());
|
||||
}
|
||||
|
||||
ProBuilderEditor.Refresh();
|
||||
}
|
||||
|
||||
GUI.backgroundColor = PreferenceKeys.proBuilderLightGray;
|
||||
UI.EditorGUIUtility.DrawSeparator(1);
|
||||
GUI.backgroundColor = Color.white;
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
EditorGUI.showMixedValue = false;
|
||||
|
||||
return EditorGUI.EndChangeCheck();
|
||||
}
|
||||
|
||||
static void UpdateDiffDictionary(ProBuilderMesh[] selection)
|
||||
{
|
||||
s_AutoUVSettingsInSelection.Clear();
|
||||
|
||||
if (selection == null || selection.Length < 1)
|
||||
return;
|
||||
|
||||
s_AutoUVSettingsInSelection = selection.SelectMany(x => x.GetSelectedFaces()).Where(x => !x.manualUV).Select(x => x.uv).ToList();
|
||||
|
||||
// Clear values for each iteration
|
||||
foreach (string key in s_AutoUVSettingsDiff.Keys.ToList())
|
||||
s_AutoUVSettingsDiff[key] = false;
|
||||
|
||||
if (s_AutoUVSettingsInSelection.Count < 1) return;
|
||||
|
||||
s_AutoUVSettings = new AutoUnwrapSettings(s_AutoUVSettingsInSelection[0]);
|
||||
|
||||
foreach (AutoUnwrapSettings u in s_AutoUVSettingsInSelection)
|
||||
{
|
||||
// if(u.projectionAxis != m_AutoUVSettings.projectionAxis)
|
||||
// m_AutoUVSettingsDiff["projectionAxis"] = true;
|
||||
if (u.useWorldSpace != s_AutoUVSettings.useWorldSpace)
|
||||
s_AutoUVSettingsDiff["useWorldSpace"] = true;
|
||||
if (u.flipU != s_AutoUVSettings.flipU)
|
||||
s_AutoUVSettingsDiff["flipU"] = true;
|
||||
if (u.flipV != s_AutoUVSettings.flipV)
|
||||
s_AutoUVSettingsDiff["flipV"] = true;
|
||||
if (u.swapUV != s_AutoUVSettings.swapUV)
|
||||
s_AutoUVSettingsDiff["swapUV"] = true;
|
||||
if (u.fill != s_AutoUVSettings.fill)
|
||||
s_AutoUVSettingsDiff["fill"] = true;
|
||||
if (!Math.Approx(u.scale.x, s_AutoUVSettings.scale.x))
|
||||
s_AutoUVSettingsDiff["scalex"] = true;
|
||||
if (!Math.Approx(u.scale.y, s_AutoUVSettings.scale.y))
|
||||
s_AutoUVSettingsDiff["scaley"] = true;
|
||||
if (!Math.Approx(u.offset.x, s_AutoUVSettings.offset.x))
|
||||
s_AutoUVSettingsDiff["offsetx"] = true;
|
||||
if (!Math.Approx(u.offset.y, s_AutoUVSettings.offset.y))
|
||||
s_AutoUVSettingsDiff["offsety"] = true;
|
||||
if (!Math.Approx(u.rotation, s_AutoUVSettings.rotation))
|
||||
s_AutoUVSettingsDiff["rotation"] = true;
|
||||
if (u.anchor != s_AutoUVSettings.anchor)
|
||||
s_AutoUVSettingsDiff["anchor"] = true;
|
||||
}
|
||||
|
||||
foreach (ProBuilderMesh pb in selection)
|
||||
{
|
||||
if (s_AutoUVSettingsDiff["manualUV"] && s_AutoUVSettingsDiff["textureGroup"])
|
||||
break;
|
||||
|
||||
Face[] selFaces = pb.GetSelectedFaces();
|
||||
|
||||
if (!s_AutoUVSettingsDiff["manualUV"])
|
||||
s_AutoUVSettingsDiff["manualUV"] = System.Array.Exists(selFaces, x => x.manualUV);
|
||||
|
||||
List<int> texGroups = selFaces.Select(x => x.textureGroup).Distinct().ToList();
|
||||
textureGroup = texGroups.FirstOrDefault(x => x > -1);
|
||||
|
||||
if (!s_AutoUVSettingsDiff["textureGroup"])
|
||||
s_AutoUVSettingsDiff["textureGroup"] = texGroups.Count() > 1;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region MODIFY SINGLE PROPERTIES
|
||||
|
||||
private static void SetFlipU(bool flipU, ProBuilderMesh[] sel)
|
||||
{
|
||||
UndoUtility.RecordSelection(sel, "Flip U");
|
||||
for (int i = 0; i < sel.Length; i++)
|
||||
{
|
||||
foreach (Face q in sel[i].GetSelectedFaces())
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.flipU = flipU;
|
||||
q.uv = uv;
|
||||
sel[i].SetGroupUV(q.uv, q.textureGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetFlipV(bool flipV, ProBuilderMesh[] sel)
|
||||
{
|
||||
UndoUtility.RecordSelection(sel, "Flip V");
|
||||
for (int i = 0; i < sel.Length; i++)
|
||||
{
|
||||
foreach (Face q in sel[i].GetSelectedFaces())
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.flipV = flipV;
|
||||
q.uv = uv;
|
||||
sel[i].SetGroupUV(q.uv, q.textureGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetSwapUV(bool swapUV, ProBuilderMesh[] sel)
|
||||
{
|
||||
UndoUtility.RecordSelection(sel, "Swap U, V");
|
||||
for (int i = 0; i < sel.Length; i++)
|
||||
{
|
||||
foreach (Face q in sel[i].GetSelectedFaces())
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.swapUV = swapUV;
|
||||
q.uv = uv;
|
||||
sel[i].SetGroupUV(q.uv, q.textureGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetUseWorldSpace(bool useWorldSpace, ProBuilderMesh[] sel)
|
||||
{
|
||||
UndoUtility.RecordSelection(sel, "Use World Space UVs");
|
||||
for (int i = 0; i < sel.Length; i++)
|
||||
{
|
||||
foreach (Face q in sel[i].GetSelectedFaces())
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.useWorldSpace = useWorldSpace;
|
||||
q.uv = uv;
|
||||
sel[i].SetGroupUV(q.uv, q.textureGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetFill(AutoUnwrapSettings.Fill fill, ProBuilderMesh[] sel)
|
||||
{
|
||||
UndoUtility.RecordSelection(sel, "Fill UVs");
|
||||
for (int i = 0; i < sel.Length; i++)
|
||||
{
|
||||
foreach (Face q in sel[i].GetSelectedFaces())
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.fill = fill;
|
||||
q.uv = uv;
|
||||
sel[i].SetGroupUV(q.uv, q.textureGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetAnchor(AutoUnwrapSettings.Anchor anchor, ProBuilderMesh[] sel)
|
||||
{
|
||||
UndoUtility.RecordSelection(sel, "Set UV Anchor");
|
||||
|
||||
for (int i = 0; i < sel.Length; i++)
|
||||
{
|
||||
foreach (Face q in sel[i].GetSelectedFaces())
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.anchor = anchor;
|
||||
q.uv = uv;
|
||||
sel[i].SetGroupUV(q.uv, q.textureGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetOffset(Vector2 offset, Axis2D axis, ProBuilderMesh[] sel)
|
||||
{
|
||||
UndoUtility.RecordSelection(sel, "Offset UVs");
|
||||
|
||||
for (int i = 0; i < sel.Length; i++)
|
||||
{
|
||||
foreach (Face q in sel[i].GetSelectedFaces())
|
||||
{
|
||||
switch (axis)
|
||||
{
|
||||
case Axis2D.XY:
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.offset = offset;
|
||||
q.uv = uv;
|
||||
break;
|
||||
}
|
||||
case Axis2D.X:
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.offset = new Vector2(offset.x, q.uv.offset.y);
|
||||
q.uv = uv;
|
||||
break;
|
||||
}
|
||||
case Axis2D.Y:
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.offset = new Vector2(q.uv.offset.x, offset.y);
|
||||
q.uv = uv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sel[i].SetGroupUV(q.uv, q.textureGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetRotation(float rot, ProBuilderMesh[] sel)
|
||||
{
|
||||
UndoUtility.RecordSelection(sel, "Rotate UVs");
|
||||
|
||||
for (int i = 0; i < sel.Length; i++)
|
||||
{
|
||||
foreach (Face q in sel[i].GetSelectedFaces())
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.rotation = rot;
|
||||
if (uv.rotation > 360f)
|
||||
uv.rotation = uv.rotation % 360f;
|
||||
else if (uv.rotation < 0f)
|
||||
uv.rotation = 360f + (uv.rotation % 360f);
|
||||
q.uv = uv;
|
||||
sel[i].SetGroupUV(uv, q.textureGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void SetScale(Vector2 scale, Axis2D axis, ProBuilderMesh[] sel)
|
||||
{
|
||||
UndoUtility.RecordSelection(sel, "Scale UVs");
|
||||
|
||||
for (int i = 0; i < sel.Length; i++)
|
||||
{
|
||||
foreach (Face q in sel[i].GetSelectedFaces())
|
||||
{
|
||||
switch (axis)
|
||||
{
|
||||
case Axis2D.XY:
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.scale = scale;
|
||||
q.uv = uv;
|
||||
break;
|
||||
}
|
||||
case Axis2D.X:
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.scale = new Vector2(scale.x, q.uv.scale.y);
|
||||
q.uv = uv;
|
||||
break;
|
||||
}
|
||||
case Axis2D.Y:
|
||||
{
|
||||
var uv = q.uv;
|
||||
uv.scale = new Vector2(q.uv.scale.x, scale.y);
|
||||
q.uv = uv;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
sel[i].SetGroupUV(q.uv, q.textureGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TEXTURE GROUPS
|
||||
|
||||
private static void SetTextureGroup(ProBuilderMesh[] selection, int tex)
|
||||
{
|
||||
UndoUtility.RecordSelection(selection, "Set Texture Group " + textureGroup);
|
||||
|
||||
foreach (ProBuilderMesh pb in selection)
|
||||
{
|
||||
if (pb.selectedFaceCount < 1)
|
||||
continue;
|
||||
|
||||
Face[] faces = pb.GetSelectedFaces();
|
||||
AutoUnwrapSettings cuv = faces[0].uv;
|
||||
|
||||
foreach (Face f in faces)
|
||||
{
|
||||
f.textureGroup = tex;
|
||||
f.uv = new AutoUnwrapSettings(cuv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void TextureGroupSelectedFaces(ProBuilderMesh pb)//, pb_Face face)
|
||||
{
|
||||
if (pb.selectedFaceCount < 1) return;
|
||||
|
||||
Face[] faces = pb.GetSelectedFaces();
|
||||
|
||||
AutoUnwrapSettings cont_uv = faces[0].uv;
|
||||
|
||||
int texGroup = pb.GetUnusedTextureGroup();
|
||||
|
||||
UndoUtility.RecordSelection(pb, "Create Texture Group" + textureGroup);
|
||||
|
||||
foreach (Face f in faces)
|
||||
{
|
||||
f.uv = new AutoUnwrapSettings(cont_uv);
|
||||
f.textureGroup = texGroup;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 74e7658b6a583464fa496ffe540ef91f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+716
@@ -0,0 +1,716 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
[CustomEditor(typeof(BezierShape))]
|
||||
sealed class BezierShapeEditor : Editor
|
||||
{
|
||||
static readonly GUIContent[] s_TangentModeIcons = new GUIContent[3];
|
||||
|
||||
const float k_HandleSize = .05f;
|
||||
|
||||
static Vector3 Vector3_Zero = new Vector3(0f, 0f, 0f);
|
||||
static Vector3 Vector3_Forward = new Vector3(0f, 0f, 1f);
|
||||
static Vector3 Vector3_Backward = new Vector3(0f, 0f, -1f);
|
||||
|
||||
static Color bezierPositionHandleColor = new Color(.01f, .8f, .99f, 1f);
|
||||
static Color bezierTangentHandleColor = new Color(.6f, .6f, .6f, .8f);
|
||||
|
||||
[SerializeField]
|
||||
BezierHandle m_currentHandle = new BezierHandle(-1, false);
|
||||
|
||||
[SerializeField]
|
||||
BezierTangentMode m_TangentMode = BezierTangentMode.Mirrored;
|
||||
|
||||
BezierShape m_Target = null;
|
||||
bool m_IsMoving = false;
|
||||
List<Vector3> m_ControlPoints;
|
||||
|
||||
ProBuilderMesh m_CurrentObject
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_Target.mesh == null)
|
||||
{
|
||||
m_Target.mesh = m_Target.gameObject.AddComponent<ProBuilderMesh>();
|
||||
EditorUtility.InitObject(m_Target.mesh);
|
||||
}
|
||||
|
||||
return m_Target.mesh;
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
struct BezierHandle
|
||||
{
|
||||
public int index;
|
||||
public bool isTangent;
|
||||
public BezierTangentDirection tangent;
|
||||
|
||||
public BezierHandle(int index, bool isTangent, BezierTangentDirection tangent = BezierTangentDirection.In)
|
||||
{
|
||||
this.index = index;
|
||||
this.isTangent = isTangent;
|
||||
this.tangent = tangent;
|
||||
}
|
||||
|
||||
public static implicit operator int(BezierHandle handle)
|
||||
{
|
||||
return handle.index;
|
||||
}
|
||||
|
||||
public static explicit operator BezierHandle(int index)
|
||||
{
|
||||
return new BezierHandle(index, false);
|
||||
}
|
||||
|
||||
public static implicit operator BezierTangentDirection(BezierHandle handle)
|
||||
{
|
||||
return handle.tangent;
|
||||
}
|
||||
|
||||
public void SetIndex(int index)
|
||||
{
|
||||
this.index = index;
|
||||
this.isTangent = false;
|
||||
}
|
||||
|
||||
public void SetIndexAndTangent(int index, BezierTangentDirection dir)
|
||||
{
|
||||
this.index = index;
|
||||
this.isTangent = true;
|
||||
this.tangent = dir;
|
||||
}
|
||||
}
|
||||
|
||||
List<BezierPoint> m_Points
|
||||
{
|
||||
get { return m_Target.points; }
|
||||
set { m_Target.points = value; }
|
||||
}
|
||||
|
||||
bool m_IsEditing
|
||||
{
|
||||
get { return m_Target.isEditing; }
|
||||
set { m_Target.isEditing = value; }
|
||||
}
|
||||
|
||||
bool m_CloseLoop
|
||||
{
|
||||
get { return m_Target.closeLoop; }
|
||||
|
||||
set
|
||||
{
|
||||
if (m_Target.closeLoop != value)
|
||||
UndoUtility.RecordObject(m_Target, "Set Bezier Shape Close Loop");
|
||||
m_Target.closeLoop = value;
|
||||
}
|
||||
}
|
||||
|
||||
float m_Radius
|
||||
{
|
||||
get { return m_Target.radius; }
|
||||
|
||||
set
|
||||
{
|
||||
if (m_Target.radius != value)
|
||||
UndoUtility.RecordObject(m_Target, "Set Bezier Shape Radius");
|
||||
m_Target.radius = value;
|
||||
}
|
||||
}
|
||||
|
||||
int m_Rows
|
||||
{
|
||||
get { return m_Target.rows; }
|
||||
|
||||
set
|
||||
{
|
||||
if (m_Target.rows != value)
|
||||
UndoUtility.RecordObject(m_Target, "Set Bezier Shape Rows");
|
||||
m_Target.rows = value;
|
||||
}
|
||||
}
|
||||
|
||||
int m_Columns
|
||||
{
|
||||
get { return m_Target.columns; }
|
||||
|
||||
set
|
||||
{
|
||||
if (m_Target.columns != value)
|
||||
UndoUtility.RecordObject(m_Target, "Set Bezier Shape Columns");
|
||||
m_Target.columns = value;
|
||||
}
|
||||
}
|
||||
|
||||
bool m_Smooth
|
||||
{
|
||||
get { return m_Target.smooth; }
|
||||
|
||||
set
|
||||
{
|
||||
if (m_Target.smooth != value)
|
||||
UndoUtility.RecordObject(m_Target, "Set Bezier Shape Smooth");
|
||||
m_Target.smooth = value;
|
||||
}
|
||||
}
|
||||
|
||||
private GUIStyle _commandStyle = null;
|
||||
|
||||
public GUIStyle commandStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_commandStyle == null)
|
||||
{
|
||||
_commandStyle = new GUIStyle(EditorGUIUtility.GetBuiltinSkin(EditorSkin.Inspector).FindStyle("Command"));
|
||||
_commandStyle.alignment = TextAnchor.MiddleCenter;
|
||||
}
|
||||
|
||||
return _commandStyle;
|
||||
}
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_Target = target as BezierShape;
|
||||
|
||||
Undo.undoRedoPerformed += this.UndoRedoPerformed;
|
||||
|
||||
s_TangentModeIcons[0] = new GUIContent(IconUtility.GetIcon("Toolbar/Bezier_Free"), "Tangent Mode: Free");
|
||||
s_TangentModeIcons[1] = new GUIContent(IconUtility.GetIcon("Toolbar/Bezier_Aligned"), "Tangent Mode: Aligned");
|
||||
s_TangentModeIcons[2] = new GUIContent(IconUtility.GetIcon("Toolbar/Bezier_Mirrored"), "Tangent Mode: Mirrored");
|
||||
|
||||
if (m_Target != null)
|
||||
SetIsEditing(m_Target.isEditing);
|
||||
|
||||
ProBuilderEditor.selectModeChanged += SelectModeChanged;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Undo.undoRedoPerformed -= this.UndoRedoPerformed;
|
||||
ProBuilderEditor.selectModeChanged -= SelectModeChanged;
|
||||
}
|
||||
|
||||
void SelectModeChanged(SelectMode mode)
|
||||
{
|
||||
if (!mode.ContainsFlag(SelectMode.InputTool) && m_IsEditing)
|
||||
{
|
||||
SetIsEditing(false);
|
||||
Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
BezierPoint DoBezierPointGUI(BezierPoint point)
|
||||
{
|
||||
Vector3 pos = point.position, tin = point.tangentIn, tout = point.tangentOut;
|
||||
|
||||
bool wasInWideMode = EditorGUIUtility.wideMode;
|
||||
float labelWidth = EditorGUIUtility.labelWidth;
|
||||
EditorGUIUtility.wideMode = true;
|
||||
EditorGUIUtility.labelWidth = EditorGUIUtility.currentViewWidth / 3f;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
pos = EditorGUILayout.Vector3Field("Position", pos);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
point.SetPosition(pos);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
tin = EditorGUILayout.Vector3Field("Tan. In", tin);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
point.SetTangentIn(tin, m_TangentMode);
|
||||
Rect r = GUILayoutUtility.GetLastRect();
|
||||
r.x += EditorGUIUtility.labelWidth - 12;
|
||||
GUI.color = Color.blue;
|
||||
GUI.Label(r, "\u2022");
|
||||
GUI.color = Color.white;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
tout = EditorGUILayout.Vector3Field("Tan. Out", tout);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
point.SetTangentOut(tout, m_TangentMode);
|
||||
r = GUILayoutUtility.GetLastRect();
|
||||
r.x += EditorGUIUtility.labelWidth - 12;
|
||||
GUI.color = Color.red;
|
||||
GUI.Label(r, "\u2022");
|
||||
GUI.color = Color.white;
|
||||
|
||||
Vector3 euler = point.rotation.eulerAngles;
|
||||
euler = EditorGUILayout.Vector3Field("Rotation", euler);
|
||||
point.rotation = Quaternion.Euler(euler);
|
||||
|
||||
EditorGUIUtility.labelWidth = labelWidth;
|
||||
EditorGUIUtility.wideMode = wasInWideMode;
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
void SetIsEditing(bool isEditing)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
|
||||
if (isEditing && !m_IsEditing)
|
||||
{
|
||||
if (ProBuilderEditor.instance != null)
|
||||
ProBuilderEditor.instance.ClearElementSelection();
|
||||
|
||||
UndoUtility.RecordObject(m_Target, "Edit Bezier Shape");
|
||||
UndoUtility.RecordObject(m_Target.mesh, "Edit Bezier Shape");
|
||||
|
||||
UpdateMesh(true);
|
||||
}
|
||||
|
||||
m_Target.isEditing = isEditing;
|
||||
|
||||
if (m_Target.isEditing)
|
||||
ProBuilderEditor.selectMode |= SelectMode.InputTool;
|
||||
else
|
||||
ProBuilderEditor.selectMode &= ~SelectMode.InputTool;
|
||||
|
||||
if (m_Target.isEditing)
|
||||
{
|
||||
Tools.current = Tool.None;
|
||||
UpdateControlPoints();
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (!m_IsEditing)
|
||||
{
|
||||
if (GUILayout.Button("Edit Bezier Shape"))
|
||||
SetIsEditing(true);
|
||||
|
||||
EditorGUILayout.HelpBox("Editing a Bezier Shape will erase any modifications made to the mesh!\n\nIf you accidentally enter Edit Mode you can Undo to get your changes back.", MessageType.Warning);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Editing Bezier Shape", UI.EditorGUIUtility.GetActiveStyle("Button")))
|
||||
SetIsEditing(false);
|
||||
|
||||
Event e = Event.current;
|
||||
|
||||
if (m_IsMoving)
|
||||
{
|
||||
if (e.type == EventType.Ignore ||
|
||||
e.type == EventType.MouseUp)
|
||||
OnFinishVertexModification();
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
bool handleIsValid = (m_currentHandle > -1 && m_currentHandle < m_Points.Count);
|
||||
|
||||
BezierPoint inspectorPoint = handleIsValid ?
|
||||
m_Points[m_currentHandle] :
|
||||
new BezierPoint(Vector3_Zero, Vector3_Backward, Vector3_Forward, Quaternion.identity);
|
||||
|
||||
inspectorPoint = DoBezierPointGUI(inspectorPoint);
|
||||
|
||||
if (handleIsValid && EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (!m_IsMoving)
|
||||
OnBeginVertexModification();
|
||||
|
||||
m_Points[m_currentHandle] = inspectorPoint;
|
||||
UpdateMesh(false);
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
if (GUILayout.Button("Clear Points"))
|
||||
{
|
||||
UndoUtility.RecordObject(m_Target, "Clear Bezier Spline Points");
|
||||
m_Points.Clear();
|
||||
UpdateMesh(true);
|
||||
}
|
||||
|
||||
if (GUILayout.Button("Add Point"))
|
||||
{
|
||||
UndoUtility.RecordObject(m_Target, "Add Bezier Spline Point");
|
||||
|
||||
if (m_Points.Count > 0)
|
||||
{
|
||||
m_Points.Add(new BezierPoint(m_Points[m_Points.Count - 1].position,
|
||||
m_Points[m_Points.Count - 1].tangentIn,
|
||||
m_Points[m_Points.Count - 1].tangentOut,
|
||||
Quaternion.identity));
|
||||
UpdateMesh(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Target.Init();
|
||||
}
|
||||
|
||||
m_currentHandle = (BezierHandle)(m_Points.Count - 1);
|
||||
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
m_TangentMode = (BezierTangentMode)GUILayout.Toolbar((int)m_TangentMode, s_TangentModeIcons, commandStyle);
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
m_CloseLoop = EditorGUILayout.Toggle("Close Loop", m_CloseLoop);
|
||||
m_Smooth = EditorGUILayout.Toggle("Smooth", m_Smooth);
|
||||
m_Radius = Mathf.Max(.001f, EditorGUILayout.FloatField("Radius", m_Radius));
|
||||
m_Rows = Math.Clamp(EditorGUILayout.IntField("Rows", m_Rows), 3, 512);
|
||||
m_Columns = Math.Clamp(EditorGUILayout.IntField("Columns", m_Columns), 3, 512);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
UpdateMesh(true);
|
||||
}
|
||||
|
||||
void UpdateMesh(bool vertexCountChanged)
|
||||
{
|
||||
if (m_Target != null)
|
||||
{
|
||||
m_Target.Refresh();
|
||||
UpdateControlPoints();
|
||||
ProBuilderEditor.Refresh(vertexCountChanged);
|
||||
}
|
||||
}
|
||||
|
||||
void UpdateControlPoints()
|
||||
{
|
||||
m_ControlPoints = Spline.GetControlPoints(m_Points, m_Columns, m_CloseLoop, null);
|
||||
}
|
||||
|
||||
void OnSceneGUI()
|
||||
{
|
||||
Event e = Event.current;
|
||||
|
||||
bool eventHasBeenUsed = false;
|
||||
|
||||
if (m_IsMoving)
|
||||
{
|
||||
if (e.type == EventType.Ignore ||
|
||||
e.type == EventType.MouseUp)
|
||||
{
|
||||
eventHasBeenUsed = true;
|
||||
OnFinishVertexModification();
|
||||
}
|
||||
}
|
||||
|
||||
bool sceneViewInUse = EditorHandleUtility.SceneViewInUse(e);
|
||||
|
||||
if (e.type == EventType.KeyDown)
|
||||
{
|
||||
if (e.keyCode == KeyCode.Backspace && m_currentHandle > -1 && m_currentHandle < m_Points.Count)
|
||||
{
|
||||
UndoUtility.RecordObject(m_Target, "Delete Bezier Point");
|
||||
m_Points.RemoveAt(m_currentHandle);
|
||||
UpdateMesh(true);
|
||||
}
|
||||
else if (e.keyCode == KeyCode.Escape)
|
||||
{
|
||||
SetIsEditing(false);
|
||||
}
|
||||
}
|
||||
|
||||
int count = m_Points.Count;
|
||||
|
||||
Matrix4x4 handleMatrix = Handles.matrix;
|
||||
Handles.matrix = m_Target.transform.localToWorldMatrix;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
if (index < count - 1 || m_CloseLoop)
|
||||
{
|
||||
Handles.DrawBezier(m_Points[index].position,
|
||||
m_Points[(index + 1) % count].position,
|
||||
m_Points[index].tangentOut,
|
||||
m_Points[(index + 1) % count].tangentIn,
|
||||
Color.green,
|
||||
EditorGUIUtility.whiteTexture,
|
||||
1f);
|
||||
}
|
||||
|
||||
if (!m_IsEditing)
|
||||
continue;
|
||||
|
||||
// If the index is selected show the full transform gizmo, otherwise use free move handles
|
||||
if (m_currentHandle == index)
|
||||
{
|
||||
BezierPoint point = m_Points[index];
|
||||
|
||||
if (!m_currentHandle.isTangent)
|
||||
{
|
||||
Vector3 prev = point.position;
|
||||
|
||||
prev = Handles.PositionHandle(prev, Quaternion.identity);
|
||||
|
||||
if (!Math.Approx3(prev, point.position))
|
||||
{
|
||||
if (!m_IsMoving)
|
||||
OnBeginVertexModification();
|
||||
|
||||
prev = EditorSnapping.MoveSnap(prev);
|
||||
|
||||
Vector3 dir = prev - point.position;
|
||||
point.position = prev;
|
||||
point.tangentIn += dir;
|
||||
point.tangentOut += dir;
|
||||
}
|
||||
|
||||
// rotation
|
||||
int prev_index = index > 0 ? index - 1 : (m_CloseLoop ? count - 1 : -1);
|
||||
int next_index = index < count - 1 ? index + 1 : (m_CloseLoop ? 0 : -1);
|
||||
Vector3 rd = BezierPoint.GetLookDirection(m_Points, index, prev_index, next_index);
|
||||
|
||||
Quaternion look = Quaternion.LookRotation(rd);
|
||||
float size = HandleUtility.GetHandleSize(point.position);
|
||||
Matrix4x4 pm = Handles.matrix;
|
||||
Handles.matrix = pm * Matrix4x4.TRS(point.position, look, Vector3.one);
|
||||
point.rotation = Handles.Disc(point.rotation, Vector3.zero, Vector3.forward, size, false, 0f);
|
||||
Handles.matrix = pm;
|
||||
}
|
||||
else
|
||||
{
|
||||
Handles.color = bezierTangentHandleColor;
|
||||
|
||||
if (m_currentHandle.tangent == BezierTangentDirection.In && (m_CloseLoop || index > 0))
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
point.tangentIn = Handles.PositionHandle(point.tangentIn, Quaternion.identity);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (!m_IsMoving)
|
||||
OnBeginVertexModification();
|
||||
|
||||
point.tangentIn = EditorSnapping.MoveSnap(point.tangentIn);
|
||||
point.EnforceTangentMode(BezierTangentDirection.In, m_TangentMode);
|
||||
}
|
||||
Handles.color = Color.blue;
|
||||
Handles.DrawLine(m_Points[index].position, m_Points[index].tangentIn);
|
||||
}
|
||||
|
||||
if (m_currentHandle.tangent == BezierTangentDirection.Out && (m_CloseLoop || index < count - 1))
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
point.tangentOut = Handles.PositionHandle(point.tangentOut, Quaternion.identity);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
if (!m_IsMoving)
|
||||
OnBeginVertexModification();
|
||||
|
||||
point.tangentOut = EditorSnapping.MoveSnap(point.tangentOut);
|
||||
point.EnforceTangentMode(BezierTangentDirection.Out, m_TangentMode);
|
||||
}
|
||||
Handles.color = Color.red;
|
||||
Handles.DrawLine(m_Points[index].position, m_Points[index].tangentOut);
|
||||
}
|
||||
}
|
||||
|
||||
m_Points[index] = point;
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_IsEditing)
|
||||
return;
|
||||
|
||||
EventType eventType = e.type;
|
||||
|
||||
if (!eventHasBeenUsed)
|
||||
eventHasBeenUsed = eventType == EventType.Used;
|
||||
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
Vector3 prev;
|
||||
BezierPoint point = m_Points[index];
|
||||
|
||||
// Position Handle
|
||||
float size = HandleUtility.GetHandleSize(point.position) * k_HandleSize;
|
||||
Handles.color = bezierPositionHandleColor;
|
||||
|
||||
if (m_currentHandle == index && !m_currentHandle.isTangent)
|
||||
{
|
||||
Handles.DotHandleCap(0, point.position, Quaternion.identity, size, e.type);
|
||||
}
|
||||
else
|
||||
{
|
||||
prev = point.position;
|
||||
prev = FreeMoveHandle(prev, size, Vector3.zero, Handles.DotHandleCap);
|
||||
if (!eventHasBeenUsed && eventType == EventType.MouseUp && e.type == EventType.Used)
|
||||
{
|
||||
eventHasBeenUsed = true;
|
||||
m_currentHandle = (BezierHandle)index;
|
||||
Repaint();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
else if (!Math.Approx3(prev, point.position))
|
||||
{
|
||||
if (!m_IsMoving)
|
||||
OnBeginVertexModification();
|
||||
|
||||
point.SetPosition(EditorSnapping.MoveSnap(prev));
|
||||
}
|
||||
}
|
||||
|
||||
// Tangent handles
|
||||
Handles.color = bezierTangentHandleColor;
|
||||
|
||||
// Tangent In Handle
|
||||
if (m_CloseLoop || index > 0)
|
||||
{
|
||||
size = HandleUtility.GetHandleSize(point.tangentIn) * k_HandleSize;
|
||||
Handles.DrawLine(point.position, point.tangentIn);
|
||||
|
||||
if (index == m_currentHandle && m_currentHandle.isTangent && m_currentHandle.tangent == BezierTangentDirection.In)
|
||||
{
|
||||
Handles.DotHandleCap(0, point.tangentIn, Quaternion.identity, size, e.type);
|
||||
}
|
||||
else
|
||||
{
|
||||
prev = point.tangentIn;
|
||||
prev = FreeMoveHandle(prev, size, Vector3.zero, Handles.DotHandleCap);
|
||||
|
||||
if (!eventHasBeenUsed && eventType == EventType.MouseUp && e.type == EventType.Used)
|
||||
{
|
||||
eventHasBeenUsed = true;
|
||||
m_currentHandle.SetIndexAndTangent(index, BezierTangentDirection.In);
|
||||
Repaint();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
else if (!Math.Approx3(prev, point.tangentIn))
|
||||
{
|
||||
if (!m_IsMoving)
|
||||
OnBeginVertexModification();
|
||||
point.tangentIn = EditorSnapping.MoveSnap(prev);
|
||||
point.EnforceTangentMode(BezierTangentDirection.In, m_TangentMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tangent Out
|
||||
if (m_CloseLoop || index < count - 1)
|
||||
{
|
||||
size = HandleUtility.GetHandleSize(point.tangentOut) * k_HandleSize;
|
||||
Handles.DrawLine(point.position, point.tangentOut);
|
||||
|
||||
if (index == m_currentHandle && m_currentHandle.isTangent && m_currentHandle.tangent == BezierTangentDirection.Out)
|
||||
{
|
||||
Handles.DotHandleCap(0, point.tangentOut, Quaternion.identity, size, e.type);
|
||||
}
|
||||
else
|
||||
{
|
||||
prev = point.tangentOut;
|
||||
prev = FreeMoveHandle(prev, size, Vector3.zero, Handles.DotHandleCap);
|
||||
|
||||
if (!eventHasBeenUsed && eventType == EventType.MouseUp && e.type == EventType.Used)
|
||||
{
|
||||
eventHasBeenUsed = true;
|
||||
m_currentHandle.SetIndexAndTangent(index, BezierTangentDirection.Out);
|
||||
Repaint();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
else if (!Math.Approx3(prev, point.tangentOut))
|
||||
{
|
||||
if (!m_IsMoving)
|
||||
OnBeginVertexModification();
|
||||
point.tangentOut = EditorSnapping.MoveSnap(prev);
|
||||
point.EnforceTangentMode(BezierTangentDirection.Out, m_TangentMode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_Points[index] = point;
|
||||
}
|
||||
|
||||
// Do control point insertion
|
||||
if (!eventHasBeenUsed && m_ControlPoints != null && m_ControlPoints.Count > 1)
|
||||
{
|
||||
int index = -1;
|
||||
float distanceToLine;
|
||||
|
||||
Vector3 p = EditorHandleUtility.ClosestPointToPolyLine(m_ControlPoints, out index, out distanceToLine, false, null);
|
||||
|
||||
if (!IsHoveringHandlePoint(e.mousePosition) && distanceToLine < PreferenceKeys.k_MaxPointDistanceFromControl)
|
||||
{
|
||||
Handles.color = Color.green;
|
||||
Handles.DotHandleCap(-1, p, Quaternion.identity, HandleUtility.GetHandleSize(p) * .05f, e.type);
|
||||
Handles.color = Color.white;
|
||||
|
||||
if (!eventHasBeenUsed && eventType == EventType.MouseDown && e.button == 0)
|
||||
{
|
||||
UndoUtility.RecordObject(m_Target, "Add Point");
|
||||
Vector3 dir = m_ControlPoints[(index + 1) % m_ControlPoints.Count] - m_ControlPoints[index];
|
||||
m_Points.Insert((index / m_Columns) + 1, new BezierPoint(p, p - dir, p + dir, Quaternion.identity));
|
||||
UpdateMesh(true);
|
||||
e.Use();
|
||||
}
|
||||
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
}
|
||||
|
||||
if (e.type == EventType.MouseUp && !sceneViewInUse)
|
||||
m_currentHandle.SetIndex(-1);
|
||||
|
||||
Handles.matrix = handleMatrix;
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
UpdateMesh(false);
|
||||
}
|
||||
|
||||
Vector3 FreeMoveHandle(Vector3 position, float size, Vector3 snap, Handles.CapFunction capFunction)
|
||||
{
|
||||
return Handles.FreeMoveHandle(position, size, snap, capFunction);
|
||||
}
|
||||
|
||||
bool IsHoveringHandlePoint(Vector2 mpos)
|
||||
{
|
||||
if (m_Target == null)
|
||||
return false;
|
||||
|
||||
int count = m_Points.Count;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
BezierPoint p = m_Points[i];
|
||||
|
||||
bool ti = m_CloseLoop || i > 0;
|
||||
bool to = m_CloseLoop || i < (count - 1);
|
||||
|
||||
if (Vector2.Distance(mpos, HandleUtility.WorldToGUIPoint(p.position)) < PreferenceKeys.k_MaxPointDistanceFromControl ||
|
||||
(ti && Vector2.Distance(mpos, HandleUtility.WorldToGUIPoint(p.tangentIn)) < PreferenceKeys.k_MaxPointDistanceFromControl) ||
|
||||
(to && Vector2.Distance(mpos, HandleUtility.WorldToGUIPoint(p.tangentOut)) < PreferenceKeys.k_MaxPointDistanceFromControl))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UndoRedoPerformed()
|
||||
{
|
||||
if (m_Target && m_IsEditing)
|
||||
{
|
||||
UpdateControlPoints();
|
||||
UpdateMesh(true);
|
||||
}
|
||||
}
|
||||
|
||||
void OnBeginVertexModification()
|
||||
{
|
||||
m_IsMoving = true;
|
||||
UndoUtility.RecordObject(m_Target, "Modify Bezier Spline");
|
||||
Lightmapping.PushGIWorkflowMode();
|
||||
}
|
||||
|
||||
void OnFinishVertexModification()
|
||||
{
|
||||
m_IsMoving = false;
|
||||
Lightmapping.PopGIWorkflowMode();
|
||||
m_CurrentObject.Optimize();
|
||||
ProBuilderEditor.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e95f50cd644f90741bb903cb56bcd67e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,384 @@
|
||||
#if PROBUILDER_EXPERIMENTAL_FEATURES
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.ProBuilder.Csg;
|
||||
using UnityEngine.ProBuilder.MeshOperations;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Editor window for accessing boolean functionality.
|
||||
/// </summary>
|
||||
sealed class BooleanEditor : ConfigurableWindow
|
||||
{
|
||||
enum BooleanOp
|
||||
{
|
||||
Intersection,
|
||||
Union,
|
||||
Subtraction
|
||||
}
|
||||
|
||||
const int k_Padding = 6;
|
||||
const int k_PreviewInset = 2;
|
||||
|
||||
GameObject m_LeftGameObject, m_RightGameObject;
|
||||
int m_PreviewHeight, m_PreviewWidth;
|
||||
|
||||
Rect m_LeftObjectField = new Rect(k_Padding + k_PreviewInset, k_Padding + k_PreviewInset, 0f, 0f);
|
||||
Rect m_RightObjectField = new Rect(0f, k_Padding + k_PreviewInset, 0f, 0f);
|
||||
Rect m_ReverseOperationOrderRect = new Rect(0f, 0f, 42f, 42f);
|
||||
|
||||
static GUIStyle previewBackground;
|
||||
static GUIStyle unicodeIconStyle;
|
||||
|
||||
Color backgroundColor = new Color(.15625f, .15625f, .15625f, 1f);
|
||||
Texture2D backgroundTexture;
|
||||
Editor m_LeftPreviewEditor, m_RightPreviewEditor;
|
||||
BooleanOp operation = BooleanOp.Intersection;
|
||||
bool mouseClickedSwapRect = false;
|
||||
Vector2Int screen = Vector2Int.zero;
|
||||
static readonly string k_ReverseArrowsIcon = ((char)8644).ToString();
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Experimental/Boolean (CSG) Tool", false, PreferenceKeys.menuMisc)]
|
||||
public static void MenuOpenBooleanTool()
|
||||
{
|
||||
GetWindow<BooleanEditor>(true, "Boolean (Experimental)", true).Show();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
minSize = new Vector2(200f, 135f);
|
||||
|
||||
var meshes = Selection.transforms.GetComponents<ProBuilderMesh>();
|
||||
|
||||
if (meshes.Length == 2)
|
||||
{
|
||||
m_LeftGameObject = meshes[0].gameObject;
|
||||
m_RightGameObject = meshes[1].gameObject;
|
||||
}
|
||||
|
||||
previewBackground = new GUIStyle();
|
||||
|
||||
backgroundTexture = new Texture2D(2, 2);
|
||||
|
||||
backgroundTexture.SetPixels(new Color[] {
|
||||
backgroundColor,
|
||||
backgroundColor,
|
||||
backgroundColor,
|
||||
backgroundColor
|
||||
});
|
||||
|
||||
backgroundTexture.Apply();
|
||||
|
||||
previewBackground.normal.background = backgroundTexture;
|
||||
|
||||
unicodeIconStyle = new GUIStyle();
|
||||
unicodeIconStyle.fontSize = 32;
|
||||
unicodeIconStyle.normal.textColor = Color.white;
|
||||
unicodeIconStyle.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
var arrowSize = unicodeIconStyle.CalcSize(UI.EditorGUIUtility.TempContent(k_ReverseArrowsIcon));
|
||||
m_ReverseOperationOrderRect.width = arrowSize.x;
|
||||
m_ReverseOperationOrderRect.width = arrowSize.y;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (backgroundTexture != null)
|
||||
{
|
||||
DestroyImmediate(backgroundTexture);
|
||||
}
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
DoContextMenu();
|
||||
|
||||
Event e = Event.current;
|
||||
screen.x = (int)position.width;
|
||||
screen.y = (int)position.height;
|
||||
|
||||
// Since image wells eat mouse clicks, listen for a mouse up when hovering over 'reverse operation order' button
|
||||
switch (e.type)
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (m_ReverseOperationOrderRect.Contains(e.mousePosition))
|
||||
{
|
||||
mouseClickedSwapRect = true;
|
||||
e.Use();
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
if (mouseClickedSwapRect && m_ReverseOperationOrderRect.Contains(Event.current.mousePosition))
|
||||
{
|
||||
ReverseOperationOrder();
|
||||
e.Use();
|
||||
}
|
||||
mouseClickedSwapRect = false;
|
||||
break;
|
||||
|
||||
case EventType.Ignore:
|
||||
mouseClickedSwapRect = false;
|
||||
break;
|
||||
}
|
||||
|
||||
DrawPreviewWells();
|
||||
|
||||
if (ListenForDragAndDrop())
|
||||
return;
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
ProBuilderMesh lpb = m_LeftGameObject != null ? m_LeftGameObject.GetComponent<ProBuilderMesh>() : null;
|
||||
ProBuilderMesh rpb = m_RightGameObject != null ? m_RightGameObject.GetComponent<ProBuilderMesh>() : null;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
lpb = (ProBuilderMesh) EditorGUILayout.ObjectField(lpb, typeof(ProBuilderMesh), true);
|
||||
rpb = (ProBuilderMesh) EditorGUILayout.ObjectField(rpb, typeof(ProBuilderMesh), true);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
DestroyImmediate(m_LeftPreviewEditor);
|
||||
DestroyImmediate(m_RightPreviewEditor);
|
||||
}
|
||||
|
||||
m_LeftGameObject = lpb != null ? lpb.gameObject : null;
|
||||
m_RightGameObject = rpb != null ? rpb.gameObject : null;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// Boolean controls
|
||||
GUILayout.Space(4);
|
||||
|
||||
GUI.backgroundColor = PreferenceKeys.proBuilderDarkGray;
|
||||
UI.EditorGUIUtility.DrawSeparator(2);
|
||||
GUI.backgroundColor = Color.white;
|
||||
|
||||
operation = (BooleanOp) EditorGUILayout.EnumPopup("Operation", operation);
|
||||
|
||||
if (GUILayout.Button("Apply"))
|
||||
{
|
||||
switch (operation)
|
||||
{
|
||||
case BooleanOp.Union:
|
||||
MenuUnion(m_LeftGameObject.GetComponent<ProBuilderMesh>(), m_RightGameObject.GetComponent<ProBuilderMesh>());
|
||||
break;
|
||||
|
||||
case BooleanOp.Intersection:
|
||||
MenuIntersect(m_LeftGameObject.GetComponent<ProBuilderMesh>(), m_RightGameObject.GetComponent<ProBuilderMesh>());
|
||||
break;
|
||||
|
||||
case BooleanOp.Subtraction:
|
||||
MenuSubtract(m_LeftGameObject.GetComponent<ProBuilderMesh>(), m_RightGameObject.GetComponent<ProBuilderMesh>());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReverseOperationOrder()
|
||||
{
|
||||
GameObject tmp = m_LeftGameObject;
|
||||
m_LeftGameObject = m_RightGameObject;
|
||||
m_RightGameObject = tmp;
|
||||
m_LeftPreviewEditor = null;
|
||||
m_RightPreviewEditor = null;
|
||||
}
|
||||
|
||||
// Draw the mesh previews
|
||||
void DrawPreviewWells()
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
m_LeftObjectField = GUILayoutUtility.GetRect(GUIContent.none, UI.EditorStyles.sceneTextBox, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
||||
m_RightObjectField = GUILayoutUtility.GetRect(GUIContent.none, UI.EditorStyles.sceneTextBox, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true));
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUI.Box(m_LeftObjectField, GUIContent.none, UI.EditorStyles.sceneTextBox);
|
||||
GUI.Box(m_RightObjectField, GUIContent.none, UI.EditorStyles.sceneTextBox);
|
||||
|
||||
m_ReverseOperationOrderRect.x = (screen.x / 2f) - (m_ReverseOperationOrderRect.width / 2f);
|
||||
m_ReverseOperationOrderRect.y = m_LeftObjectField.y + m_LeftObjectField.height * .5f - m_ReverseOperationOrderRect.height * .5f;
|
||||
|
||||
m_LeftObjectField = InsetRect(m_LeftObjectField, k_PreviewInset);
|
||||
m_RightObjectField = InsetRect(m_RightObjectField, k_PreviewInset);
|
||||
|
||||
if (m_LeftGameObject != null)
|
||||
{
|
||||
if (m_LeftPreviewEditor == null)
|
||||
m_LeftPreviewEditor = UnityEditor.Editor.CreateEditor(m_LeftGameObject);
|
||||
m_LeftPreviewEditor.OnPreviewGUI(m_LeftObjectField, previewBackground);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.Label(m_LeftObjectField, "Drag GameObject Here", EditorStyles.centeredGreyMiniLabel);
|
||||
}
|
||||
|
||||
if (m_RightGameObject != null)
|
||||
{
|
||||
if (m_RightPreviewEditor == null)
|
||||
m_RightPreviewEditor = UnityEditor.Editor.CreateEditor(m_RightGameObject);
|
||||
|
||||
m_RightPreviewEditor.OnPreviewGUI(m_RightObjectField, previewBackground);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.Label(m_RightObjectField, "Drag GameObject Here", EditorStyles.centeredGreyMiniLabel);
|
||||
}
|
||||
|
||||
// Show text summary
|
||||
if (m_LeftGameObject && m_RightGameObject)
|
||||
{
|
||||
var title = UI.EditorGUIUtility.TempContent(
|
||||
operation == BooleanOp.Intersection
|
||||
? m_LeftGameObject.name + " Intersects " + m_RightGameObject.name
|
||||
: operation == BooleanOp.Union
|
||||
? m_LeftGameObject.name + " Union " + m_RightGameObject.name
|
||||
: m_LeftGameObject.name + " Subtracts " + m_RightGameObject.name);
|
||||
|
||||
var size = EditorStyles.boldLabel.CalcSize(title);
|
||||
|
||||
GUI.Label(new Rect(k_Padding + 2, k_Padding + 2, screen.x, size.y), title, EditorStyles.boldLabel);
|
||||
}
|
||||
|
||||
// http://xahlee.info/comp/unicode_arrows.html
|
||||
if (GUI.Button(m_ReverseOperationOrderRect, k_ReverseArrowsIcon, unicodeIconStyle))
|
||||
ReverseOperationOrder();
|
||||
}
|
||||
|
||||
static Rect InsetRect(Rect rect, int pad)
|
||||
{
|
||||
return new Rect(rect.x + pad, rect.y + pad, rect.width - pad * 2, rect.height - pad * 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accept drags into window.
|
||||
* MUST BE CALLED AFTER PREVIEW WELL RECTS ARE CALCULATED
|
||||
*/
|
||||
bool ListenForDragAndDrop()
|
||||
{
|
||||
Vector2 mPos = Event.current.mousePosition;
|
||||
|
||||
bool inLeft = m_LeftObjectField.Contains(mPos);
|
||||
|
||||
if (!inLeft && !m_RightObjectField.Contains(mPos))
|
||||
return false;
|
||||
|
||||
if ((Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform) && DragAndDrop.objectReferences.Length > 0)
|
||||
{
|
||||
DragAndDrop.visualMode = DragAndDropVisualMode.Copy;
|
||||
|
||||
if (Event.current.type == EventType.DragPerform)
|
||||
{
|
||||
DragAndDrop.AcceptDrag();
|
||||
|
||||
foreach (Object pb in DragAndDrop.objectReferences)
|
||||
{
|
||||
if ((pb is GameObject && ((GameObject)pb).GetComponent<ProBuilderMesh>()) || pb is ProBuilderMesh)
|
||||
{
|
||||
if (pb == m_LeftGameObject || pb == m_RightGameObject) continue;
|
||||
|
||||
if (inLeft)
|
||||
{
|
||||
m_LeftGameObject = (GameObject)pb;
|
||||
|
||||
if (m_LeftPreviewEditor != null)
|
||||
{
|
||||
DestroyImmediate(m_LeftPreviewEditor);
|
||||
m_LeftPreviewEditor = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_RightGameObject = (GameObject)pb;
|
||||
|
||||
if (m_RightPreviewEditor != null)
|
||||
{
|
||||
DestroyImmediate(m_RightPreviewEditor);
|
||||
m_RightPreviewEditor = null;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
enum BooleanOperation
|
||||
{
|
||||
Union,
|
||||
Subtract,
|
||||
Intersect
|
||||
}
|
||||
|
||||
static ActionResult MenuBooleanOperation(BooleanOperation operation, ProBuilderMesh lhs, ProBuilderMesh rhs)
|
||||
{
|
||||
if (lhs == null || rhs == null)
|
||||
return new ActionResult(ActionResult.Status.Failure, "Must Select 2 Objects");
|
||||
|
||||
string op_string = operation == BooleanOperation.Union ? "Union" : (operation == BooleanOperation.Subtract ? "Subtract" : "Intersect");
|
||||
|
||||
ProBuilderMesh[] sel = new ProBuilderMesh[] { lhs, rhs };
|
||||
|
||||
UndoUtility.RecordSelection(sel, op_string);
|
||||
|
||||
UnityEngine.ProBuilder.Csg.Model result;
|
||||
|
||||
switch (operation)
|
||||
{
|
||||
case BooleanOperation.Union:
|
||||
result = CSG.Union(lhs.gameObject, rhs.gameObject);
|
||||
break;
|
||||
|
||||
case BooleanOperation.Subtract:
|
||||
result = CSG.Subtract(lhs.gameObject, rhs.gameObject);
|
||||
break;
|
||||
|
||||
default:
|
||||
result = CSG.Intersect(lhs.gameObject, rhs.gameObject);
|
||||
break;
|
||||
}
|
||||
|
||||
var materials = result.materials.ToArray();
|
||||
ProBuilderMesh pb = ProBuilderMesh.Create();
|
||||
pb.GetComponent<MeshFilter>().sharedMesh = (Mesh) result;
|
||||
pb.GetComponent<MeshRenderer>().sharedMaterials = materials;
|
||||
MeshImporter importer = new MeshImporter(pb.gameObject);
|
||||
importer.Import(new MeshImportSettings() { quads = true, smoothing = true, smoothingAngle = 1f });
|
||||
pb.Rebuild();
|
||||
pb.CenterPivot(null);
|
||||
Selection.objects = new Object[] { pb.gameObject };
|
||||
|
||||
return new ActionResult(ActionResult.Status.Success, op_string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Union operation between two ProBuilder objects.
|
||||
*/
|
||||
public static ActionResult MenuUnion(ProBuilderMesh lhs, ProBuilderMesh rhs)
|
||||
{
|
||||
return MenuBooleanOperation(BooleanOperation.Union, lhs, rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtract boolean operation between two pb_Objects.
|
||||
*/
|
||||
public static ActionResult MenuSubtract(ProBuilderMesh lhs, ProBuilderMesh rhs)
|
||||
{
|
||||
return MenuBooleanOperation(BooleanOperation.Subtract, lhs, rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Intersect boolean operation between two pb_Objects.
|
||||
*/
|
||||
public static ActionResult MenuIntersect(ProBuilderMesh lhs, ProBuilderMesh rhs)
|
||||
{
|
||||
return MenuBooleanOperation(BooleanOperation.Intersect, lhs, rhs);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff4f787bd1deaee4d82db29948ed6099
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
abstract class ConfigurableWindow : EditorWindow, IHasCustomMenu
|
||||
{
|
||||
protected virtual bool defaultIsUtilityWindow
|
||||
{
|
||||
get { return false; }
|
||||
}
|
||||
|
||||
string utilityWindowKey
|
||||
{
|
||||
get { return GetType().ToString() + "-isUtilityWindow"; }
|
||||
}
|
||||
|
||||
protected static bool IsUtilityWindow<T>(bool defaultIsUtility = false) where T : ConfigurableWindow
|
||||
{
|
||||
return ProBuilderSettings.Get<bool>(typeof(T).ToString() + "-isUtilityWindow", SettingsScope.Project, defaultIsUtility);
|
||||
}
|
||||
|
||||
public static new T GetWindow<T>(string title, bool focus = true) where T : ConfigurableWindow
|
||||
{
|
||||
return EditorWindow.GetWindow<T>(IsUtilityWindow<T>(), title, focus);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get or create an instance of EditorWindow. Note that `utility` may be overridden by user set preference.
|
||||
/// </summary>
|
||||
public static new T GetWindow<T>(bool utility, string title, bool focus) where T : ConfigurableWindow
|
||||
{
|
||||
return EditorWindow.GetWindow<T>(IsUtilityWindow<T>(utility), title, focus);
|
||||
}
|
||||
|
||||
public virtual void AddItemsToMenu(GenericMenu menu)
|
||||
{
|
||||
bool floating = ProBuilderSettings.Get<bool>(utilityWindowKey, SettingsScope.Project, false);
|
||||
|
||||
if (menu.GetItemCount() > 1)
|
||||
menu.AddSeparator("");
|
||||
|
||||
menu.AddItem(new GUIContent("Open as Floating Window", ""), floating, () => SetIsUtilityWindow(true));
|
||||
menu.AddItem(new GUIContent("Open as Dockable Window", ""), !floating, () => SetIsUtilityWindow(false));
|
||||
|
||||
menu.AddSeparator("");
|
||||
}
|
||||
|
||||
protected void DoContextMenu()
|
||||
{
|
||||
var e = Event.current;
|
||||
|
||||
if (e.type == EventType.ContextClick)
|
||||
{
|
||||
var menu = new GenericMenu();
|
||||
AddItemsToMenu(menu);
|
||||
menu.ShowAsContext();
|
||||
}
|
||||
}
|
||||
|
||||
void SetIsUtilityWindow(bool isUtilityWindow)
|
||||
{
|
||||
ProBuilderSettings.Set<bool>(utilityWindowKey, isUtilityWindow, SettingsScope.Project);
|
||||
ProBuilderSettings.Save();
|
||||
var title = titleContent;
|
||||
Close();
|
||||
var res = GetWindow(GetType(), isUtilityWindow);
|
||||
res.titleContent = title;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f3ab6033657acd04e93e395ba82570ef
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
class ContextClickManipulator : MouseManipulator
|
||||
{
|
||||
readonly Action m_Action;
|
||||
// m_Active is necessary because we want context clicks to fire on mouse up, and CanStopManipulation only checks
|
||||
// that the mouse button matches the current activator. in our case, the modifier is rmb + alt, so "CanStop"
|
||||
// passes regardless of modifier state or "CanStart" result.
|
||||
bool m_Active;
|
||||
|
||||
public ContextClickManipulator(Action action)
|
||||
{
|
||||
m_Action = action;
|
||||
m_Active = false;
|
||||
|
||||
activators.Add(new ManipulatorActivationFilter() {
|
||||
button = MouseButton.RightMouse
|
||||
});
|
||||
|
||||
activators.Add(new ManipulatorActivationFilter() {
|
||||
button = MouseButton.LeftMouse, modifiers = EventModifiers.Alt
|
||||
});
|
||||
}
|
||||
|
||||
protected override void RegisterCallbacksOnTarget()
|
||||
{
|
||||
target.RegisterCallback<MouseDownEvent>(OnMouseDown);
|
||||
target.RegisterCallback<MouseUpEvent>(OnMouseUp);
|
||||
}
|
||||
|
||||
protected override void UnregisterCallbacksFromTarget()
|
||||
{
|
||||
target.UnregisterCallback<MouseDownEvent>(OnMouseDown);
|
||||
target.UnregisterCallback<MouseUpEvent>(OnMouseUp);
|
||||
}
|
||||
|
||||
void OnMouseDown(MouseDownEvent evt)
|
||||
{
|
||||
if (!CanStartManipulation(evt))
|
||||
return;
|
||||
m_Active = true;
|
||||
evt.StopPropagation();
|
||||
}
|
||||
|
||||
void OnMouseUp(MouseUpEvent evt)
|
||||
{
|
||||
if (CanStopManipulation(evt) && m_Active)
|
||||
{
|
||||
m_Action();
|
||||
evt.StopPropagation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e7bacce7eace4935a5e59e52618fc826
|
||||
timeCreated: 1692299202
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d14f7e2694bf3944989ac7742561181c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,62 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Listens for pb_Object::OnDestroy events and deletes or ignores meshes depending on whether or not the mesh is an asset.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
static class DestroyListener
|
||||
{
|
||||
static DestroyListener()
|
||||
{
|
||||
ProBuilderMesh.meshWillBeDestroyed -= OnDestroyObject;
|
||||
ProBuilderMesh.meshWillBeDestroyed += OnDestroyObject;
|
||||
}
|
||||
|
||||
static void OnDestroyObject(ProBuilderMesh mesh)
|
||||
{
|
||||
if (Experimental.meshesAreAssets)
|
||||
{
|
||||
if (EditorUtility.IsPrefab(mesh))
|
||||
{
|
||||
// Debug.Log("will not destroy prefab mesh");
|
||||
}
|
||||
else
|
||||
{
|
||||
string cache_path;
|
||||
Mesh cache_mesh;
|
||||
|
||||
// if it is cached but not a prefab instance or root, destroy the mesh in the cache
|
||||
// otherwise go ahead and destroy as usual
|
||||
if (EditorMeshUtility.GetCachedMesh(mesh, out cache_path, out cache_mesh))
|
||||
{
|
||||
// on entering / exiting play mode unity instances everything and destroys the scene,
|
||||
// which nukes the mesh cache. don't do this.
|
||||
bool isPlaying = EditorApplication.isPlaying;
|
||||
bool orWillPlay = EditorApplication.isPlayingOrWillChangePlaymode;
|
||||
|
||||
if (isPlaying || orWillPlay)
|
||||
return;
|
||||
|
||||
SelectionUtility.Remove(mesh);
|
||||
AssetDatabase.DeleteAsset(cache_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
Object.DestroyImmediate(mesh.mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
string path = AssetDatabase.GetAssetPath(mesh.mesh);
|
||||
|
||||
// If the pb_Object is backed by a Mesh asset don't destroy it.
|
||||
if (string.IsNullOrEmpty(path))
|
||||
Object.DestroyImmediate(mesh.mesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2197ea2611109104aa25635ed7b59bcb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,596 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.SettingsManagement;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
[FilePath("Library/Probuilder/DimensionsOverlay", FilePathAttribute.Location.ProjectFolder)]
|
||||
sealed class DimensionsEditor : ScriptableSingleton<DimensionsEditor>
|
||||
{
|
||||
struct Trs : IEquatable<Trs>
|
||||
{
|
||||
public Vector3 position { get; set; }
|
||||
public Quaternion rotation { get; set; }
|
||||
public Vector3 scale { get; set; }
|
||||
|
||||
public Trs(Transform t)
|
||||
{
|
||||
position = t.position;
|
||||
rotation = t.rotation;
|
||||
scale = t.localScale;
|
||||
}
|
||||
|
||||
public bool Equals(Trs other)
|
||||
{
|
||||
return position.Equals(other.position) && rotation.Equals(other.rotation) && scale.Equals(other.scale);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is Trs other && Equals(other);
|
||||
}
|
||||
|
||||
public static explicit operator Trs(Transform trs)
|
||||
{
|
||||
return new Trs(trs);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
unchecked
|
||||
{
|
||||
var hashCode = position.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ rotation.GetHashCode();
|
||||
hashCode = (hashCode * 397) ^ scale.GetHashCode();
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool m_HasBounds;
|
||||
Bounds m_Bounds;
|
||||
Dictionary<Transform, Trs> m_Selected = new Dictionary<Transform, Trs>();
|
||||
// When ProBuilder is modifying a mesh, it doesn't recalculate the mesh bounds. This lets our bounds encapsulation
|
||||
// function know that Renderer.bounds is not to be trusted.
|
||||
static bool s_RecalculateMeshBounds;
|
||||
|
||||
[SerializeField]
|
||||
bool m_Visible;
|
||||
|
||||
public bool visible
|
||||
{
|
||||
get => m_Visible;
|
||||
set
|
||||
{
|
||||
if (m_Visible == value)
|
||||
return;
|
||||
|
||||
m_Visible = value;
|
||||
if (m_Visible)
|
||||
OnShowed();
|
||||
else
|
||||
OnHid();
|
||||
|
||||
Save(true);
|
||||
}
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Dimensions Overlay/Hide", true, PreferenceKeys.menuEditor + 30)]
|
||||
static bool HideVerify()
|
||||
{
|
||||
return instance.visible;
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Dimensions Overlay/Hide", false, PreferenceKeys.menuEditor + 30)]
|
||||
static void Hide()
|
||||
{
|
||||
instance.visible = false;
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Dimensions Overlay/Show", true, PreferenceKeys.menuEditor + 30)]
|
||||
static bool ShowVerify()
|
||||
{
|
||||
return !instance.visible;
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Dimensions Overlay/Show", false, PreferenceKeys.menuEditor + 30)]
|
||||
static void Show()
|
||||
{
|
||||
instance.visible = true;
|
||||
}
|
||||
|
||||
enum BoundsDisplay
|
||||
{
|
||||
Object = 1,
|
||||
Element = 2
|
||||
}
|
||||
|
||||
[UserSetting("Dimensions Overlay", "Bounds Display", "Sets what content is considered when calculating the selected" +
|
||||
"bounds.\nObject displays the world space bounds of the selection.\nElement displays the world space bounds" +
|
||||
"of the selected mesh elements (vertices, faces, edges).")]
|
||||
static Pref<BoundsDisplay> s_BoundsDisplay = new Pref<BoundsDisplay>("s_BoundsDisplay", BoundsDisplay.Object, SettingsScope.User);
|
||||
|
||||
[Shortcut("ProBuilder/Dimensions Overlay/Toggle Overlay", typeof(SceneView))]
|
||||
static void ToggleUseElementBounds()
|
||||
{
|
||||
// toggle between { Off, Visible Object, Visible Selection }
|
||||
if (instance.visible)
|
||||
{
|
||||
var display = s_BoundsDisplay.value;
|
||||
|
||||
// Visible Object -> Visible Selection
|
||||
if (display == BoundsDisplay.Object)
|
||||
s_BoundsDisplay.SetValue(BoundsDisplay.Element, true);
|
||||
// Visible Selection -> Off
|
||||
else
|
||||
instance.visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Off -> Visible Object
|
||||
s_BoundsDisplay.SetValue(BoundsDisplay.Object, true);
|
||||
Show();
|
||||
}
|
||||
|
||||
if (instance.visible)
|
||||
{
|
||||
instance.RebuildBounds();
|
||||
EditorUtility.ShowNotification("Dimensions Overlay\n" + s_BoundsDisplay.value.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorUtility.ShowNotification("Dimensions Overlay\nOff");
|
||||
}
|
||||
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_DisplayMesh = new Mesh();
|
||||
m_DisplayMaterial = new Material(Shader.Find("ProBuilder/UnlitVertexColor"));
|
||||
m_DisplayMesh.hideFlags = HideFlags.DontSave;
|
||||
m_DisplayMaterial.hideFlags = HideFlags.DontSave;
|
||||
|
||||
if (m_Visible)
|
||||
EditorApplication.delayCall += OnShowed; //Rebuild bounds on the first frame after entering play mode returns no bounds (the selection isn't valid yet)
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
DestroyImmediate(m_DisplayMesh);
|
||||
DestroyImmediate(m_DisplayMaterial);
|
||||
}
|
||||
|
||||
void OnShowed()
|
||||
{
|
||||
SceneView.duringSceneGui += OnSceneGUI;
|
||||
MeshSelection.objectSelectionChanged += OnObjectSelectionChanged;
|
||||
ProBuilderMesh.elementSelectionChanged += OnElementSelectionChanged;
|
||||
ProBuilderEditor.selectionUpdated += OnEditingMeshSelection;
|
||||
VertexManipulationTool.beforeMeshModification += OnBeginMeshModification;
|
||||
VertexManipulationTool.afterMeshModification += OnFinishMeshModification;
|
||||
|
||||
RebuildBounds();
|
||||
}
|
||||
|
||||
void OnHid()
|
||||
{
|
||||
MeshSelection.objectSelectionChanged -= OnObjectSelectionChanged;
|
||||
ProBuilderMesh.elementSelectionChanged -= OnElementSelectionChanged;
|
||||
ProBuilderEditor.selectionUpdated -= OnEditingMeshSelection;
|
||||
VertexManipulationTool.beforeMeshModification -= OnBeginMeshModification;
|
||||
VertexManipulationTool.afterMeshModification -= OnFinishMeshModification;
|
||||
SceneView.duringSceneGui -= OnSceneGUI;
|
||||
}
|
||||
|
||||
static void OnBeginMeshModification(IEnumerable<ProBuilderMesh> meshes)
|
||||
{
|
||||
s_RecalculateMeshBounds = true;
|
||||
}
|
||||
|
||||
static void OnFinishMeshModification(IEnumerable<ProBuilderMesh> meshes)
|
||||
{
|
||||
s_RecalculateMeshBounds = false;
|
||||
}
|
||||
|
||||
static bool GetElementBounds(IEnumerable<ProBuilderMesh> meshes, SelectMode mode, out Bounds bounds)
|
||||
{
|
||||
bool initialized = false;
|
||||
bounds = new Bounds();
|
||||
|
||||
foreach (var mesh in meshes)
|
||||
{
|
||||
var positions = mesh.positionsInternal;
|
||||
var trs = mesh.transform;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case SelectMode.Face:
|
||||
case SelectMode.TextureFace:
|
||||
{
|
||||
var faces = mesh.facesInternal;
|
||||
|
||||
foreach (var face in mesh.selectedFaceIndicesInternal)
|
||||
{
|
||||
foreach (var index in faces[face].distinctIndexesInternal)
|
||||
{
|
||||
var position = trs.TransformPoint(positions[index]);
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
bounds = new Bounds(position, Vector3.zero);
|
||||
initialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.Encapsulate(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case SelectMode.Edge:
|
||||
case SelectMode.TextureEdge:
|
||||
{
|
||||
foreach (var edge in mesh.selectedEdgesInternal)
|
||||
{
|
||||
var a = trs.TransformPoint(positions[edge.a]);
|
||||
var b = trs.TransformPoint(positions[edge.b]);
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
bounds = new Bounds(a, Vector3.zero);
|
||||
initialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.Encapsulate(a);
|
||||
}
|
||||
|
||||
bounds.Encapsulate(b);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SelectMode.Vertex:
|
||||
case SelectMode.TextureVertex:
|
||||
{
|
||||
foreach (var index in mesh.selectedIndexesInternal)
|
||||
{
|
||||
var position = trs.TransformPoint(positions[index]);
|
||||
|
||||
if (!initialized)
|
||||
{
|
||||
bounds = new Bounds(position, Vector3.zero);
|
||||
initialized = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bounds.Encapsulate(position);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return initialized;
|
||||
}
|
||||
|
||||
bool GetSelectedBounds(out Bounds bounds)
|
||||
{
|
||||
m_Selected.Clear();
|
||||
|
||||
var selectMode = ProBuilderEditor.selectMode;
|
||||
|
||||
if (s_BoundsDisplay.value == BoundsDisplay.Element && ProBuilderEditor.selectMode.IsMeshElementMode())
|
||||
{
|
||||
foreach (var m in MeshSelection.topInternal)
|
||||
m_Selected.Add(m.transform, new Trs(m.transform));
|
||||
|
||||
return GetElementBounds(MeshSelection.topInternal, selectMode, out bounds);
|
||||
}
|
||||
|
||||
foreach (var m in Selection.transforms)
|
||||
m_Selected.Add(m.transform, new Trs(m.transform));
|
||||
|
||||
if (s_RecalculateMeshBounds)
|
||||
{
|
||||
foreach(var mesh in MeshSelection.topInternal)
|
||||
mesh.mesh.RecalculateBounds();
|
||||
}
|
||||
|
||||
var renderers = Selection.transforms
|
||||
.Where(x => x.GetComponent<MeshRenderer>() != null)
|
||||
.Select(x => x.GetComponent<MeshRenderer>());
|
||||
|
||||
if (!renderers.Any())
|
||||
{
|
||||
bounds = new Bounds();
|
||||
return false;
|
||||
}
|
||||
|
||||
bounds = renderers.First().bounds;
|
||||
|
||||
foreach (var ren in renderers)
|
||||
bounds.Encapsulate(ren.bounds);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void OnObjectSelectionChanged()
|
||||
{
|
||||
RebuildBounds();
|
||||
}
|
||||
|
||||
void OnElementSelectionChanged(ProBuilderMesh mesh)
|
||||
{
|
||||
RebuildBounds();
|
||||
}
|
||||
|
||||
void OnEditingMeshSelection(IEnumerable<ProBuilderMesh> meshes)
|
||||
{
|
||||
RebuildBounds();
|
||||
}
|
||||
|
||||
void RebuildBounds()
|
||||
{
|
||||
m_HasBounds = GetSelectedBounds(out m_Bounds);
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
void OnSceneGUI(SceneView scnview)
|
||||
{
|
||||
if (Selection.count > 0 && m_HasBounds)
|
||||
{
|
||||
foreach (var m in m_Selected)
|
||||
{
|
||||
if (!((Trs)m.Key).Equals(m.Value))
|
||||
{
|
||||
RebuildBounds();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RenderBounds(m_Bounds);
|
||||
}
|
||||
}
|
||||
|
||||
Mesh m_DisplayMesh;
|
||||
Material m_DisplayMaterial;
|
||||
|
||||
// readonly Color wirecolor = new Color(.9f, .9f, .9f, .6f);
|
||||
readonly Color k_LightWhite = new Color(.6f, .6f, .6f, .5f);
|
||||
|
||||
/// <summary>
|
||||
/// Render an axis aligned bounding box in world space.
|
||||
/// </summary>
|
||||
/// <param name="bounds">aabb</param>
|
||||
void RenderBounds(Bounds bounds)
|
||||
{
|
||||
if (!m_DisplayMesh)
|
||||
return;
|
||||
|
||||
// show labels
|
||||
DrawHeight(bounds.center, bounds.extents);
|
||||
DrawWidth(bounds.center, bounds.extents);
|
||||
DrawDepth(bounds.center, bounds.extents);
|
||||
}
|
||||
|
||||
const float DISTANCE_LINE_OFFSET = .2f;
|
||||
|
||||
static float LineDistance()
|
||||
{
|
||||
return HandleUtility.GetHandleSize(Selection.activeTransform.position) * DISTANCE_LINE_OFFSET;
|
||||
}
|
||||
|
||||
Transform cam { get { return SceneView.lastActiveSceneView.camera.transform; } }
|
||||
|
||||
void DrawHeight(Vector3 cen, Vector3 ext)
|
||||
{
|
||||
// positibilities
|
||||
Vector3[] edges = new Vector3[8]
|
||||
{
|
||||
// front left
|
||||
new Vector3(cen.x - ext.x, cen.y - ext.y, cen.z - ext.z),
|
||||
new Vector3(cen.x - ext.x, cen.y + ext.y, cen.z - ext.z),
|
||||
|
||||
// front right
|
||||
new Vector3(cen.x + ext.x, cen.y - ext.y, cen.z - ext.z),
|
||||
new Vector3(cen.x + ext.x, cen.y + ext.y, cen.z - ext.z),
|
||||
|
||||
// back left
|
||||
new Vector3(cen.x - ext.x, cen.y - ext.y, cen.z + ext.z),
|
||||
new Vector3(cen.x - ext.x, cen.y + ext.y, cen.z + ext.z),
|
||||
|
||||
// back right
|
||||
new Vector3(cen.x + ext.x, cen.y - ext.y, cen.z + ext.z),
|
||||
new Vector3(cen.x + ext.x, cen.y + ext.y, cen.z + ext.z)
|
||||
};
|
||||
|
||||
// figure leftmost height boundary
|
||||
Vector2 pos = Vector2.right * 20000f;
|
||||
Vector3 a = Vector3.zero, b = Vector3.zero;
|
||||
|
||||
for (int i = 0; i < edges.Length; i += 2)
|
||||
{
|
||||
Vector2 screen = HandleUtility.WorldToGUIPoint((edges[i] + edges[i + 1]) * .5f);
|
||||
|
||||
if (screen.x < pos.x)
|
||||
{
|
||||
pos = screen;
|
||||
a = edges[i + 0];
|
||||
b = edges[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
float dist = Vector3.Distance(a, b);
|
||||
|
||||
if (dist < Mathf.Epsilon)
|
||||
return;
|
||||
|
||||
Vector3 left = Vector3.Cross(cam.forward, Vector3.up).normalized * LineDistance();
|
||||
|
||||
Handles.color = k_LightWhite;
|
||||
Handles.DrawLine(a + left * .1f, a + left);
|
||||
Handles.DrawLine(b + left * .1f, b + left);
|
||||
Handles.color = Color.green;
|
||||
Handles.DrawLine(a + left, b + left);
|
||||
|
||||
a += left;
|
||||
b += left;
|
||||
|
||||
Handles.BeginGUI();
|
||||
pos.x -= UI.EditorStyles.sceneTextBox.CalcSize(gc).x * 2f;
|
||||
DrawSceneLabel(dist.ToString("F2"), pos);
|
||||
Handles.EndGUI();
|
||||
}
|
||||
|
||||
void DrawDepth(Vector3 cen, Vector3 ext)
|
||||
{
|
||||
// positibilities
|
||||
Vector3[] edges = new Vector3[8]
|
||||
{
|
||||
// bottom right
|
||||
new Vector3(cen.x + ext.x, cen.y - ext.y, cen.z + ext.z),
|
||||
new Vector3(cen.x + ext.x, cen.y - ext.y, cen.z - ext.z),
|
||||
|
||||
// top right
|
||||
new Vector3(cen.x + ext.x, cen.y + ext.y, cen.z + ext.z),
|
||||
new Vector3(cen.x + ext.x, cen.y + ext.y, cen.z - ext.z),
|
||||
|
||||
// bottom left
|
||||
new Vector3(cen.x - ext.x, cen.y - ext.y, cen.z + ext.z),
|
||||
new Vector3(cen.x - ext.x, cen.y - ext.y, cen.z - ext.z),
|
||||
|
||||
// top left
|
||||
new Vector3(cen.x - ext.x, cen.y + ext.y, cen.z + ext.z),
|
||||
new Vector3(cen.x - ext.x, cen.y + ext.y, cen.z - ext.z),
|
||||
};
|
||||
|
||||
// figure leftmost height boundary
|
||||
Vector2 pos = Vector2.up * -20000f;
|
||||
Vector3 a = Vector3.zero, b = Vector3.zero;
|
||||
|
||||
for (int i = 0; i < edges.Length; i += 2)
|
||||
{
|
||||
Vector2 screen = HandleUtility.WorldToGUIPoint((edges[i] + edges[i + 1]) * .5f);
|
||||
|
||||
if (screen.y > pos.y)
|
||||
{
|
||||
pos = screen;
|
||||
a = edges[i + 0];
|
||||
b = edges[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
float dist = Vector3.Distance(a, b);
|
||||
|
||||
if (dist < Mathf.Epsilon)
|
||||
return;
|
||||
|
||||
float dot = Vector3.Dot(cam.transform.forward, Vector3.right);
|
||||
float sign = dot < 0f ? -1f : 1f;
|
||||
Vector3 offset = -(Vector3.up + (Vector3.right * sign)).normalized * LineDistance();
|
||||
|
||||
Handles.color = k_LightWhite;
|
||||
Handles.DrawLine(a + offset * .1f, a + offset);
|
||||
Handles.DrawLine(b + offset * .1f, b + offset);
|
||||
|
||||
a += offset;
|
||||
b += offset;
|
||||
|
||||
Handles.color = Color.blue;
|
||||
Handles.DrawLine(a, b);
|
||||
|
||||
Handles.BeginGUI();
|
||||
pos.y += UI.EditorStyles.sceneTextBox.CalcHeight(gc, 20000);
|
||||
DrawSceneLabel(dist.ToString("F2"), pos);
|
||||
|
||||
Handles.EndGUI();
|
||||
}
|
||||
|
||||
void DrawWidth(Vector3 cen, Vector3 extents)
|
||||
{
|
||||
Vector3 ext = extents;// + extents.normalized * .2f;
|
||||
|
||||
// positibilities
|
||||
Vector3[] edges = new Vector3[8]
|
||||
{
|
||||
// bottom front
|
||||
new Vector3(cen.x - ext.x, cen.y - ext.y, cen.z - ext.z),
|
||||
new Vector3(cen.x + ext.x, cen.y - ext.y, cen.z - ext.z),
|
||||
|
||||
// bottom back
|
||||
new Vector3(cen.x - ext.x, cen.y - ext.y, cen.z + ext.z),
|
||||
new Vector3(cen.x + ext.x, cen.y - ext.y, cen.z + ext.z),
|
||||
|
||||
// top front
|
||||
new Vector3(cen.x - ext.x, cen.y + ext.y, cen.z - ext.z),
|
||||
new Vector3(cen.x + ext.x, cen.y + ext.y, cen.z - ext.z),
|
||||
|
||||
// top back
|
||||
new Vector3(cen.x - ext.x, cen.y + ext.y, cen.z + ext.z),
|
||||
new Vector3(cen.x + ext.x, cen.y + ext.y, cen.z + ext.z)
|
||||
};
|
||||
|
||||
// figure leftmost height boundary
|
||||
Vector2 pos = Vector2.up * -20000f;
|
||||
Vector3 a = Vector3.zero, b = Vector3.zero;
|
||||
|
||||
for (int i = 0; i < edges.Length; i += 2)
|
||||
{
|
||||
Vector2 screen = HandleUtility.WorldToGUIPoint((edges[i] + edges[i + 1]) * .5f);
|
||||
|
||||
if (screen.y > pos.y)
|
||||
{
|
||||
pos = screen;
|
||||
a = edges[i + 0];
|
||||
b = edges[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
float dist = Vector3.Distance(a, b);
|
||||
|
||||
if (dist < Mathf.Epsilon)
|
||||
return;
|
||||
// Vector3 offset = -Vector3.up;
|
||||
// offset = -Vector3.Cross(Vector3.Cross(cam.forward, Vector3.up), cam.forward).normalized * LineDistance();
|
||||
|
||||
float dot = Vector3.Dot(cam.transform.forward, Vector3.forward);
|
||||
float sign = dot < 0f ? -1f : 1f;
|
||||
Vector3 offset = -(Vector3.up + (Vector3.forward * sign)).normalized * LineDistance();
|
||||
|
||||
Handles.color = k_LightWhite;
|
||||
Handles.DrawLine(a + offset * .1f, a + offset);
|
||||
Handles.DrawLine(b + offset * .1f, b + offset);
|
||||
|
||||
a += offset;
|
||||
b += offset;
|
||||
|
||||
Handles.color = Color.red;
|
||||
Handles.DrawLine(a, b);
|
||||
Handles.BeginGUI();
|
||||
DrawSceneLabel(dist.ToString("F2"), HandleUtility.WorldToGUIPoint((a + b) * .5f));
|
||||
Handles.EndGUI();
|
||||
}
|
||||
|
||||
GUIContent gc = new GUIContent("", "");
|
||||
|
||||
void DrawSceneLabel(string content, Vector2 position)
|
||||
{
|
||||
gc.text = content;
|
||||
float width = UI.EditorStyles.sceneTextBox.CalcSize(gc).x;
|
||||
float height = UI.EditorStyles.sceneTextBox.CalcHeight(gc, width);
|
||||
GUI.Label(new Rect(position.x, position.y, width, height), gc, UI.EditorStyles.sceneTextBox);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1ececafedc41a4b4bb1be308a1512857
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,894 @@
|
||||
using System;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.ProBuilder.Shapes;
|
||||
using UObject = UnityEngine.Object;
|
||||
using Plane = UnityEngine.ProBuilder.Shapes.Plane;
|
||||
using Sprite = UnityEngine.ProBuilder.Shapes.Sprite;
|
||||
using ToolManager = UnityEditor.EditorTools.ToolManager;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
|
||||
[EditorTool("Create Cube", variantGroup = typeof(DrawShapeTool), variantPriority = 0)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Cube.png")]
|
||||
class CreateCubeTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Cube %#K", false, PreferenceKeys.menuEditor + 1)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreateCubeTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Cube Shape", nameof(CreateCubeTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Cube", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Cube", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Cube", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Cube))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Cube));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Sphere",variantGroup = typeof(DrawShapeTool), variantPriority = 1)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Sphere.png")]
|
||||
class CreateSphereTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Sphere", false, PreferenceKeys.menuEditor + 2)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreateSphereTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Sphere Shape", nameof(CreateSphereTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Sphere", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Sphere", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Sphere", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Sphere))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Sphere));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Plane", variantGroup = typeof(DrawShapeTool), variantPriority = 2)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Plane.png")]
|
||||
class CreatePlaneTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Plane", false, PreferenceKeys.menuEditor + 3)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreatePlaneTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Plane Shape", nameof(CreatePlaneTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Plane", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Plane", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Plane", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Plane))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Plane));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Cylinder",variantGroup = typeof(DrawShapeTool), variantPriority = 3)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Cylinder.png")]
|
||||
class CreateCylinderTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Cylinder", false, PreferenceKeys.menuEditor + 4)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreateCylinderTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Cylinder Shape", nameof(CreateCylinderTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Cylinder", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Cylinder", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Cylinder", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Cylinder))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Cylinder));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Cone", variantGroup = typeof(DrawShapeTool), variantPriority = 4)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Cone.png")]
|
||||
class CreateConeTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Cone", false, PreferenceKeys.menuEditor + 5)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreateConeTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Cone Shape", nameof(CreateConeTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Cone", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Cone", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Cone", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Cone))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Cone));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Prism",variantGroup = typeof(DrawShapeTool), variantPriority = 5)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Prism.png")]
|
||||
class CreatePrismTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Prism", false, PreferenceKeys.menuEditor + 6)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreatePrismTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Prism Shape", nameof(CreatePrismTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Prism", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Prism", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Prism", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Prism))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Prism));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Stairs", variantGroup = typeof(DrawShapeTool), variantPriority = 6)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Stairs.png")]
|
||||
class CreateStairsTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Stairs", false, PreferenceKeys.menuEditor + 7)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreateStairsTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Stairs Shape", nameof(CreateStairsTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Stairs", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Stairs", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Stairs", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Stairs))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Stairs));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Torus",variantGroup = typeof(DrawShapeTool), variantPriority = 7)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Torus.png")]
|
||||
class CreateTorusTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Torus", false, PreferenceKeys.menuEditor + 8)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreateTorusTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Torus Shape", nameof(CreateTorusTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Torus", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Torus", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Torus", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Torus))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Torus));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Pipe",variantGroup = typeof(DrawShapeTool), variantPriority = 8)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Pipe.png")]
|
||||
class CreatePipeTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Pipe", false, PreferenceKeys.menuEditor + 9)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreatePipeTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Pipe Shape", nameof(CreatePipeTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Pipe", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Pipe", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Pipe", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Pipe))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Pipe));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Arch",variantGroup = typeof(DrawShapeTool), variantPriority = 9)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Arch.png")]
|
||||
class CreateArchTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Arch", false, PreferenceKeys.menuEditor + 10)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreateArchTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Arch Shape", nameof(CreateArchTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Arch", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Arch", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Arch", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Arch))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Arch));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Door",variantGroup = typeof(DrawShapeTool), variantPriority = 10)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Door.png")]
|
||||
class CreateDoorTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Door", false, PreferenceKeys.menuEditor + 11)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreateDoorTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Door Shape", nameof(CreateDoorTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Door", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Door", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Door", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Door))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Door));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
[EditorTool("Create Sprite",variantGroup = typeof(DrawShapeTool), variantPriority = 11)]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/ShapeTool/Sprite.png")]
|
||||
class CreateSpriteTool : DrawShapeTool
|
||||
{
|
||||
[MenuItem(EditorToolbarMenuItem.k_MenuPrefix + "Editors/Create Shape/Sprite", false, PreferenceKeys.menuEditor + 12)]
|
||||
static void MenuPerform_NewShape()
|
||||
{
|
||||
ToolManager.SetActiveTool<CreateSpriteTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("New Sprite Shape", nameof(CreateSpriteTool));
|
||||
}
|
||||
|
||||
Pref<Vector3> m_LastSize = new Pref<Vector3>("ShapeBuilder.LastSize.Sprite", Vector3.one);
|
||||
protected override Vector3 lastSize { get => m_LastSize.value; set => m_LastSize.SetValue(value); }
|
||||
|
||||
Pref<Quaternion> m_LastRotation = new Pref<Quaternion>("ShapeBuilder.LastRotation.Sprite", Quaternion.identity);
|
||||
protected override Quaternion lastRotation { get => m_LastRotation.value; set => m_LastRotation.SetValue(value); }
|
||||
|
||||
Pref<PivotLocation> m_PivotLocation = new Pref<PivotLocation>("ShapeBuilder.PivotLocation.Sprite", PivotLocation.Center);
|
||||
protected override PivotLocation shapePivotLocation { get => m_PivotLocation.value; set => m_PivotLocation.SetValue(value); }
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
if (m_LastShapeCreated && !(m_LastShapeCreated.shape is Sprite))
|
||||
m_LastShapeCreated = null;
|
||||
s_ActiveShapeIndex.value = Array.IndexOf(EditorShapeUtility.availableShapeTypes, typeof(Sprite));
|
||||
base.OnActivated();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class DrawShapeTool : EditorTool
|
||||
{
|
||||
internal const int k_MinOverlayWidth = 300;
|
||||
|
||||
ShapeState m_CurrentState;
|
||||
|
||||
protected internal ProBuilderShape m_LastShapeCreated = null;
|
||||
|
||||
internal ProBuilderShape m_ProBuilderShape;
|
||||
internal bool m_IsShapeInit;
|
||||
|
||||
Editor m_ShapeEditor;
|
||||
|
||||
bool m_HandleSelectionChanges = false;
|
||||
|
||||
internal bool handleSelectionChange
|
||||
{
|
||||
set
|
||||
{
|
||||
if(m_HandleSelectionChanges == value)
|
||||
return;
|
||||
|
||||
m_HandleSelectionChanges = value;
|
||||
if(value)
|
||||
MeshSelection.objectSelectionChanged += OnSelectionChanged;
|
||||
else
|
||||
MeshSelection.objectSelectionChanged -= OnSelectionChanged;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Plane of interaction
|
||||
internal UnityEngine.Plane m_Plane;
|
||||
internal Vector3 m_PlaneForward, m_PlaneRight;
|
||||
internal Quaternion m_PlaneRotation;
|
||||
internal Vector3 m_BB_Origin, m_BB_OppositeCorner, m_BB_HeightCorner;
|
||||
|
||||
// Shape's duplicate
|
||||
internal GameObject m_DuplicateGO = null;
|
||||
|
||||
//Shape's properties
|
||||
internal bool m_IsOnGrid;
|
||||
|
||||
internal Bounds m_Bounds;
|
||||
static readonly Color k_BoundsColor = new Color(.2f, .4f, .8f, 1f);
|
||||
|
||||
static readonly GUIContent k_ShapeTitle = new GUIContent("Shape Settings");
|
||||
|
||||
internal static Pref<int> s_ActiveShapeIndex = new Pref<int>("ShapeBuilder.ActiveShapeIndex", 0);
|
||||
public static Pref<bool> s_SettingsEnabled = new Pref<bool>("ShapeComponent.SettingsEnabled", false, SettingsScope.Project);
|
||||
|
||||
|
||||
protected abstract Vector3 lastSize { get; set; }
|
||||
protected abstract Quaternion lastRotation { get; set; }
|
||||
protected abstract PivotLocation shapePivotLocation { get; set; }
|
||||
|
||||
public PivotLocation pivotLocation
|
||||
{
|
||||
get => shapePivotLocation;
|
||||
set
|
||||
{
|
||||
if (value != shapePivotLocation)
|
||||
{
|
||||
if (instance != null && instance.m_LastShapeCreated != null && instance.currentShapeInOverlay == instance.m_LastShapeCreated)
|
||||
{
|
||||
var lastShape = instance.m_LastShapeCreated;
|
||||
var lastShapeTrs = lastShape.transform;
|
||||
var newPivotPosition = lastShape.shapeWorldCenter;
|
||||
if (value != PivotLocation.Center)
|
||||
newPivotPosition += instance.m_PlaneRotation * m_LastNonDuplicateCenterToOrigin;
|
||||
|
||||
instance.m_LastShapeCreated.Rebuild(newPivotPosition, lastShapeTrs.rotation, lastShape.shapeWorldBounds);
|
||||
}
|
||||
shapePivotLocation = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Vector3 m_LastNonDuplicateCenterToOrigin;
|
||||
PivotLocation m_LastPreviewPivotLocation;
|
||||
|
||||
internal Vector3 previewPivotPosition
|
||||
{
|
||||
get
|
||||
{
|
||||
if (pivotLocation == PivotLocation.FirstVertex && instance != null)
|
||||
{
|
||||
var lastCenterToOrigin = instance.m_LastNonDuplicateCenterToOrigin;
|
||||
var lastCenterToOriginNorm = lastCenterToOrigin.normalized;
|
||||
|
||||
var deltaRot = instance.m_PlaneRotation;
|
||||
lastCenterToOriginNorm = deltaRot * lastCenterToOriginNorm;
|
||||
|
||||
var pivotOffset = lastCenterToOriginNorm * lastCenterToOrigin.magnitude;
|
||||
return pivotOffset + instance.m_Bounds.center;
|
||||
}
|
||||
|
||||
return m_Bounds.center;
|
||||
}
|
||||
}
|
||||
|
||||
int m_ControlID;
|
||||
|
||||
internal float minSnapSize
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_IsOnGrid)
|
||||
{
|
||||
return Mathf.Min(EditorSnapping.activeMoveSnapValue.x,
|
||||
Mathf.Min(EditorSnapping.activeMoveSnapValue.y,
|
||||
EditorSnapping.activeMoveSnapValue.z));
|
||||
}
|
||||
|
||||
return Mathf.Min(EditorSnapping.incrementalSnapMoveValue.x,
|
||||
Mathf.Min(EditorSnapping.incrementalSnapMoveValue.y,
|
||||
EditorSnapping.incrementalSnapMoveValue.z));
|
||||
}
|
||||
}
|
||||
|
||||
const float k_MinBoundLength = 0.001f; // 1mm
|
||||
|
||||
// ideally this would be owned by the state machine
|
||||
public int controlID => m_ControlID;
|
||||
|
||||
public static Type activeShapeType
|
||||
{
|
||||
get { return s_ActiveShapeIndex < 0 ? typeof(Cube) : EditorShapeUtility.availableShapeTypes[s_ActiveShapeIndex]; }
|
||||
}
|
||||
|
||||
public override bool gridSnapEnabled => true;
|
||||
|
||||
internal ProBuilderShape currentShapeInOverlay
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_CurrentState is ShapeState_InitShape && m_LastShapeCreated != null)
|
||||
return m_LastShapeCreated;
|
||||
|
||||
if (m_CurrentState is ShapeState_DrawBaseShape &&
|
||||
m_DuplicateGO != null && m_DuplicateGO.GetComponent<MeshRenderer>().enabled)
|
||||
return m_DuplicateGO.GetComponent<ProBuilderShape>();
|
||||
|
||||
return proBuilderShape;
|
||||
}
|
||||
}
|
||||
|
||||
internal ProBuilderShape proBuilderShape
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ProBuilderShape == null)
|
||||
{
|
||||
m_ProBuilderShape = new GameObject("Shape", typeof(ProBuilderShape)).GetComponent<ProBuilderShape>();
|
||||
m_ProBuilderShape.gameObject.hideFlags = HideFlags.HideAndDontSave;
|
||||
m_ProBuilderShape.hideFlags = HideFlags.None;
|
||||
m_ProBuilderShape.SetShape(EditorShapeUtility.CreateShape(activeShapeType));
|
||||
m_ProBuilderShape.size = lastSize;
|
||||
m_ProBuilderShape.shapeRotation = lastRotation;
|
||||
}
|
||||
return m_ProBuilderShape;
|
||||
}
|
||||
}
|
||||
|
||||
static DrawShapeTool s_Instance = null;
|
||||
internal static DrawShapeTool instance => s_Instance;
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (m_ShapeEditor != null)
|
||||
DestroyImmediate(m_ShapeEditor);
|
||||
}
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
m_ProBuilderShape = null;
|
||||
|
||||
MeshSelection.SetSelection((GameObject)null);
|
||||
handleSelectionChange = true;
|
||||
|
||||
Undo.undoRedoPerformed += HandleUndoRedoPerformed;
|
||||
ToolManager.activeToolChanged += OnActiveToolChanged;
|
||||
ToolManager.activeContextChanged += OnActiveContextChanged;
|
||||
ProBuilderEditor.selectModeChanged += OnSelectModeChanged;
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
|
||||
if (m_CurrentState == null)
|
||||
m_CurrentState = InitStateMachine();
|
||||
else
|
||||
m_CurrentState = ShapeState.ResetTool(this);
|
||||
|
||||
s_Instance = this;
|
||||
}
|
||||
|
||||
public override void OnWillBeDeactivated()
|
||||
{
|
||||
s_Instance = null;
|
||||
handleSelectionChange = false;
|
||||
m_LastShapeCreated = null;
|
||||
Undo.undoRedoPerformed -= HandleUndoRedoPerformed;
|
||||
ToolManager.activeToolChanged -= OnActiveToolChanged;
|
||||
ToolManager.activeContextChanged -= OnActiveContextChanged;
|
||||
ProBuilderEditor.selectModeChanged -= OnSelectModeChanged;
|
||||
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
||||
|
||||
if (m_ProBuilderShape != null)
|
||||
m_CurrentState = ShapeState.ResetState();
|
||||
|
||||
if (m_DuplicateGO != null)
|
||||
DestroyImmediate(m_DuplicateGO);
|
||||
}
|
||||
|
||||
void OnSelectModeChanged(SelectMode mode)
|
||||
{
|
||||
ToolManager.RestorePreviousPersistentTool();
|
||||
}
|
||||
|
||||
void OnActiveToolChanged()
|
||||
{
|
||||
if(ToolManager.IsActiveTool(this))
|
||||
SetBounds(currentShapeInOverlay.size);
|
||||
}
|
||||
|
||||
void OnActiveContextChanged()
|
||||
{
|
||||
if(ToolManager.activeContextType != typeof(GameObjectToolContext))
|
||||
ToolManager.RestorePreviousPersistentTool();
|
||||
}
|
||||
|
||||
private void OnPlayModeStateChanged(PlayModeStateChange state)
|
||||
{
|
||||
if (state == PlayModeStateChange.ExitingEditMode || state == PlayModeStateChange.ExitingPlayMode)
|
||||
{
|
||||
// Reset tool state when entering/exiting playmode
|
||||
if (ToolManager.IsActiveTool(this))
|
||||
{
|
||||
m_CurrentState = ShapeState.ResetState();
|
||||
ToolManager.RestorePreviousPersistentTool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HandleUndoRedoPerformed()
|
||||
{
|
||||
if(ToolManager.IsActiveTool(this))
|
||||
m_CurrentState = ShapeState.ResetState();
|
||||
}
|
||||
|
||||
void OnSelectionChanged()
|
||||
{
|
||||
if(ToolManager.IsActiveTool(this))
|
||||
{
|
||||
if(Selection.activeGameObject != null
|
||||
&& (m_LastShapeCreated == null || Selection.activeGameObject != m_LastShapeCreated.gameObject))
|
||||
{
|
||||
m_CurrentState = ShapeState.ResetState();
|
||||
ToolManager.RestorePreviousPersistentTool();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Init the state machine associated to the tool.
|
||||
/// All states are linked together and initialized.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Returns the current state of the StateMachine,
|
||||
/// this state machine will self-handle during its lifetime.
|
||||
/// </returns>
|
||||
ShapeState InitStateMachine()
|
||||
{
|
||||
ShapeState.tool = this;
|
||||
ShapeState initState = new ShapeState_InitShape();
|
||||
ShapeState drawBaseState = new ShapeState_DrawBaseShape();
|
||||
ShapeState drawHeightState = new ShapeState_DrawHeightShape();
|
||||
ShapeState.s_defaultState = initState;
|
||||
initState.m_nextState = drawBaseState;
|
||||
drawBaseState.m_nextState = drawHeightState;
|
||||
drawHeightState.m_nextState = initState;
|
||||
|
||||
return ShapeState.StartStateMachine();
|
||||
}
|
||||
|
||||
internal void SaveShapeParams(ProBuilderShape proBuilderShape)
|
||||
{
|
||||
lastSize = proBuilderShape.size;
|
||||
lastRotation = proBuilderShape.shapeRotation;
|
||||
|
||||
EditorShapeUtility.SaveParams(proBuilderShape.shape);
|
||||
}
|
||||
|
||||
internal void ApplyPrefsSettings(ProBuilderShape pBShape)
|
||||
{
|
||||
pBShape.size = lastSize;
|
||||
pBShape.shapeRotation = lastRotation;
|
||||
}
|
||||
|
||||
// Transform the point according to the snapping settings
|
||||
public Vector3 GetPoint(Vector3 point, bool useIncrementSnap = false)
|
||||
{
|
||||
if(useIncrementSnap)
|
||||
return ProBuilderSnapping.Snap(point, EditorSnapping.incrementalSnapMoveValue);
|
||||
|
||||
if (m_IsOnGrid && EditorSnapSettings.gridSnapActive)
|
||||
return ProBuilderSnapping.Snap(point, EditorSnapping.activeMoveSnapValue);
|
||||
|
||||
return point;
|
||||
}
|
||||
|
||||
internal void SetBounds(Vector3 size)
|
||||
{
|
||||
//Keep orientation created using mouse drag
|
||||
var dragDirection = m_BB_OppositeCorner - m_BB_Origin;
|
||||
float x = dragDirection.x < 0 ? -size.x : size.x;
|
||||
float z = dragDirection.z < 0 ? -size.z : size.z;
|
||||
|
||||
m_BB_OppositeCorner = m_BB_Origin + new Vector3(x, 0, z);
|
||||
m_BB_HeightCorner = m_BB_Origin + size;
|
||||
}
|
||||
|
||||
void UpdatePlaneRotation()
|
||||
{
|
||||
m_PlaneRotation = Mathf.Approximately(m_PlaneForward.sqrMagnitude, 0f) ? Quaternion.identity : Quaternion.LookRotation(m_PlaneForward, m_Plane.normal);
|
||||
}
|
||||
|
||||
internal void DoDuplicateShapePreviewHandle(Vector3 position)
|
||||
{
|
||||
var evt = Event.current;
|
||||
|
||||
if(evt.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
bool previewShortcutActive = evt.shift && !(evt.control || evt.command);
|
||||
|
||||
if (HandleUtility.nearestControl != m_ControlID || !previewShortcutActive)
|
||||
{
|
||||
if(m_DuplicateGO)
|
||||
m_DuplicateGO.GetComponent<MeshRenderer>().enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var size = currentShapeInOverlay.size;
|
||||
m_Bounds.size = size;
|
||||
position = GetPoint(position);
|
||||
var cornerPosition = position - size / 2f;
|
||||
cornerPosition.y = position.y;
|
||||
|
||||
m_Bounds.center = cornerPosition + new Vector3(size.x / 2f, 0, size.z / 2f) + (size.y / 2f) * m_Plane.normal;
|
||||
var lastPreviewRotation = m_PlaneRotation;
|
||||
UpdatePlaneRotation();
|
||||
var forceRebuildPreview = !m_PlaneRotation.Equals(lastPreviewRotation) ||
|
||||
m_LastPreviewPivotLocation != pivotLocation;
|
||||
m_LastPreviewPivotLocation = pivotLocation;
|
||||
|
||||
var preview_BB_Origin = m_Bounds.center - m_PlaneRotation * (size / 2f);
|
||||
var preview_BB_HeightCorner = m_Bounds.center + m_PlaneRotation * (size / 2f);
|
||||
var preview_BB_OppositeCorner = preview_BB_HeightCorner - m_PlaneRotation * new Vector3(0, size.y, 0);
|
||||
|
||||
|
||||
ProBuilderShape shape;
|
||||
if (m_DuplicateGO == null)
|
||||
{
|
||||
var instantiated = ShapeFactory.Instantiate(activeShapeType);
|
||||
shape = instantiated.GetComponent<ProBuilderShape>();
|
||||
m_DuplicateGO = shape.gameObject;
|
||||
m_DuplicateGO.hideFlags = HideFlags.DontSave | HideFlags.HideInHierarchy;
|
||||
ApplyPrefsSettings(shape);
|
||||
shape.GetComponent<MeshRenderer>().sharedMaterial = BuiltinMaterials.ShapePreviewMaterial;
|
||||
|
||||
EditorShapeUtility.CopyLastParams(shape.shape, shape.shape.GetType());
|
||||
shape.Rebuild(previewPivotPosition, m_PlaneRotation, m_Bounds);
|
||||
|
||||
ProBuilderEditor.Refresh(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var rendererWasEnabled = true;
|
||||
if (m_DuplicateGO.TryGetComponent<MeshRenderer>(out var renderer) && !renderer.enabled)
|
||||
{
|
||||
rendererWasEnabled = false;
|
||||
renderer.enabled = true;
|
||||
}
|
||||
|
||||
if (forceRebuildPreview || !rendererWasEnabled)
|
||||
{
|
||||
shape = m_DuplicateGO.GetComponent<ProBuilderShape>();
|
||||
|
||||
ApplyPrefsSettings(shape);
|
||||
EditorShapeUtility.CopyLastParams(shape.shape, shape.shape.GetType());
|
||||
shape.Rebuild(previewPivotPosition, m_PlaneRotation, m_Bounds);
|
||||
|
||||
ProBuilderEditor.Refresh(false);
|
||||
}
|
||||
}
|
||||
|
||||
var pivot = GetPoint(position);
|
||||
if (pivotLocation == PivotLocation.Center)
|
||||
pivot += .5f * size.y * m_Plane.normal;
|
||||
else
|
||||
pivot = previewPivotPosition;
|
||||
m_DuplicateGO.transform.SetPositionAndRotation(pivot, m_PlaneRotation);
|
||||
|
||||
DrawBoundingBox(preview_BB_Origin, preview_BB_HeightCorner, preview_BB_OppositeCorner, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalculates the bounding box for this mesh's shape.
|
||||
/// </summary>
|
||||
void RecalculateBounds()
|
||||
{
|
||||
var diag = m_BB_OppositeCorner - m_BB_Origin;
|
||||
var forward = Vector3.Dot(diag, m_PlaneForward.normalized);
|
||||
var right = Vector3.Dot(diag, m_PlaneRight.normalized);
|
||||
|
||||
var localHeight = Quaternion.Inverse(m_PlaneRotation) * (m_BB_HeightCorner - m_BB_OppositeCorner);
|
||||
var height = localHeight.y;
|
||||
|
||||
m_Bounds.size = forward * Vector3.forward + right * Vector3.right + height * Vector3.up;
|
||||
m_Bounds.center = m_BB_Origin + 0.5f * ( m_BB_OppositeCorner - m_BB_Origin ) + 0.5f * (m_BB_HeightCorner - m_BB_OppositeCorner);
|
||||
|
||||
//Prevent Z-fighting with the drawing surface
|
||||
if(Mathf.Abs(m_Bounds.center.y) < 0.0001f)
|
||||
m_Bounds.center = m_Bounds.center + 0.0001f * Vector3.up;
|
||||
|
||||
UpdatePlaneRotation();
|
||||
}
|
||||
|
||||
internal void RebuildShape()
|
||||
{
|
||||
RecalculateBounds();
|
||||
|
||||
if(m_Bounds.size.sqrMagnitude <= Mathf.Min(.01f , minSnapSize*minSnapSize)
|
||||
|| Mathf.Abs(m_Bounds.extents.x) < k_MinBoundLength
|
||||
|| Mathf.Abs(m_Bounds.extents.z) < k_MinBoundLength)
|
||||
{
|
||||
if(m_ProBuilderShape != null
|
||||
&& m_ProBuilderShape.mesh.vertexCount > 0)
|
||||
{
|
||||
m_ProBuilderShape.size = Vector3.zero;
|
||||
m_ProBuilderShape.mesh.Clear();
|
||||
m_ProBuilderShape.mesh.Rebuild();
|
||||
m_ProBuilderShape.Rebuild(new Bounds(m_BB_Origin, Vector3.zero), m_PlaneRotation);
|
||||
ProBuilderEditor.Refresh(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_IsShapeInit)
|
||||
{
|
||||
var shapeComponent = currentShapeInOverlay;
|
||||
EditorShapeUtility.CopyLastParams(shapeComponent.shape, shapeComponent.shape.GetType());
|
||||
shapeComponent.gameObject.hideFlags = HideFlags.HideInHierarchy;
|
||||
shapeComponent.mesh.renderer.sharedMaterial = EditorMaterialUtility.GetUserMaterial();
|
||||
shapeComponent.shapeRotation = Quaternion.identity;
|
||||
shapeComponent.gameObject.name = EditorShapeUtility.GetName(shapeComponent.shape);
|
||||
m_IsShapeInit = true;
|
||||
}
|
||||
|
||||
proBuilderShape.Rebuild(pivotLocation == PivotLocation.Center ? m_Bounds.center : m_BB_Origin, m_PlaneRotation, m_Bounds);
|
||||
// PBLD-137 - continously refresh the material, otherwise if Resident Drawer is on, new faces are drawn without material applied until there's some other trigger:
|
||||
// renderer enable/disable, material re-apply, etc.
|
||||
m_ProBuilderShape.mesh.renderer.sharedMaterial = EditorMaterialUtility.GetUserMaterial();
|
||||
|
||||
ProBuilderEditor.Refresh(false);
|
||||
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
//A current problem on EditorTools with MacOS seems to be calling OnToolGUI before OnActivated after a domain reload.
|
||||
if(s_Instance == null)
|
||||
return;
|
||||
|
||||
// todo refactor overlays to use `Overlay` class
|
||||
#pragma warning disable 618
|
||||
SceneViewOverlay.Window(k_ShapeTitle, OnOverlayGUI, 0, SceneViewOverlay.WindowDisplayOption.OneWindowPerTitle);
|
||||
#pragma warning restore 618
|
||||
|
||||
var evt = Event.current;
|
||||
if (EditorHandleUtility.SceneViewInUse(evt))
|
||||
return;
|
||||
|
||||
m_ControlID = GUIUtility.GetControlID(FocusType.Passive);
|
||||
HandleUtility.AddDefaultControl(m_ControlID);
|
||||
|
||||
if(GUIUtility.hotControl == 0)
|
||||
EditorGUIUtility.AddCursorRect(new Rect(0, 0, Screen.width, Screen.height), MouseCursor.ArrowPlus);
|
||||
|
||||
m_CurrentState = m_CurrentState.DoState(evt);
|
||||
}
|
||||
|
||||
internal void DrawBoundingBox(Vector3 origin, Vector3 oppositeCorner, Vector3 heightCorner, bool drawCorners = true)
|
||||
{
|
||||
using (new Handles.DrawingScope(k_BoundsColor, Matrix4x4.TRS(m_Bounds.center, m_PlaneRotation.normalized, Vector3.one)))
|
||||
{
|
||||
Handles.DrawWireCube(Vector3.zero, m_Bounds.size);
|
||||
}
|
||||
|
||||
if(!drawCorners)
|
||||
return;
|
||||
|
||||
using (new Handles.DrawingScope(Color.white))
|
||||
{
|
||||
Handles.DotHandleCap(-1, origin, Quaternion.identity, HandleUtility.GetHandleSize(origin) * 0.05f, EventType.Repaint);
|
||||
Handles.DotHandleCap(-1, oppositeCorner, Quaternion.identity, HandleUtility.GetHandleSize(oppositeCorner) * 0.05f, EventType.Repaint);
|
||||
}
|
||||
using (new Handles.DrawingScope(EditorHandleDrawing.vertexSelectedColor))
|
||||
{
|
||||
Handles.DotHandleCap(-1, heightCorner, Quaternion.identity, HandleUtility.GetHandleSize(heightCorner) * 0.05f, EventType.Repaint);
|
||||
}
|
||||
}
|
||||
|
||||
internal void DrawBoundingBox(bool drawCorners = true)
|
||||
{
|
||||
DrawBoundingBox(m_BB_Origin, m_BB_OppositeCorner, m_BB_HeightCorner, drawCorners);
|
||||
}
|
||||
|
||||
void OnOverlayGUI(UObject overlayTarget, SceneView view)
|
||||
{
|
||||
string foldoutName = $"Shape Properties (New {currentShapeInOverlay.shape.GetType().Name})";
|
||||
if(currentShapeInOverlay == m_LastShapeCreated)
|
||||
foldoutName = "Shape Properties (" + m_LastShapeCreated.name + ")";
|
||||
|
||||
Editor.CreateCachedEditor(currentShapeInOverlay, typeof(ProBuilderShapeEditor), ref m_ShapeEditor);
|
||||
|
||||
GUILayout.BeginVertical(GUILayout.MinWidth(k_MinOverlayWidth));
|
||||
((ProBuilderShapeEditor)m_ShapeEditor).m_ShapePropertyLabel.text = foldoutName;
|
||||
((ProBuilderShapeEditor)m_ShapeEditor).DrawShapeParametersGUI(this);
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 624538515085b4f498dc59dc27eb69ce
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,525 @@
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.ProBuilder.Shapes;
|
||||
using Math = UnityEngine.ProBuilder.Math;
|
||||
using Object = UnityEngine.Object;
|
||||
using FaceData = UnityEditor.ProBuilder.EditorShapeUtility.FaceData;
|
||||
using Plane = UnityEngine.ProBuilder.Shapes.Plane;
|
||||
using Sprite = UnityEngine.ProBuilder.Shapes.Sprite;
|
||||
using ToolManager = UnityEditor.EditorTools.ToolManager;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
[EditorTool("Edit Shape", typeof(ProBuilderShape))]
|
||||
[Icon("Packages/com.unity.probuilder/Editor Default Resources/Icons/Tools/EditShape.png")]
|
||||
sealed class EditShapeTool : EditorTool
|
||||
{
|
||||
[MenuItem("Tools/ProBuilder/Edit/Edit Shape", true, PreferenceKeys.menuEditor + 10)]
|
||||
static bool ValidateEditShapeTool()
|
||||
{
|
||||
foreach (var go in Selection.gameObjects)
|
||||
if (go.TryGetComponent<ProBuilderShape>(out _)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MenuItem("Tools/ProBuilder/Edit/Edit Shape", false, PreferenceKeys.menuEditor + 10)]
|
||||
static void ActivateEditShapeTool()
|
||||
{
|
||||
ToolManager.SetActiveTool<EditShapeTool>();
|
||||
ProBuilderAnalytics.SendActionEvent("Edit Shape Tool", nameof(EditShapeTool));
|
||||
}
|
||||
|
||||
Editor m_ShapeEditor;
|
||||
|
||||
static readonly Color k_BoundsHandleColor = new Color(.2f, .4f, .8f, 1f);
|
||||
|
||||
GUIContent m_OverlayTitle;
|
||||
|
||||
static FaceData[] s_Faces;
|
||||
|
||||
public static FaceData[] faces
|
||||
{
|
||||
get
|
||||
{
|
||||
if(s_Faces == null)
|
||||
{
|
||||
s_Faces = new FaceData[6];
|
||||
for(int i = 0; i < s_Faces.Length; i++)
|
||||
s_Faces[i] = new FaceData();
|
||||
}
|
||||
return s_Faces;
|
||||
}
|
||||
}
|
||||
|
||||
//Handle Manipulation
|
||||
static int s_CurrentId = -1;
|
||||
static readonly int[] k_OrientationControlIDs = new int[4];
|
||||
static int[] s_FaceControlIDs = new int[6];
|
||||
|
||||
//Size Handle management
|
||||
static Vector2 s_StartMousePosition;
|
||||
static Vector3 s_StartSize;
|
||||
static Vector3 s_StartPositionLocal;
|
||||
static Vector3 s_StartPositionGlobal;
|
||||
static Vector3 s_StartScale;
|
||||
static Vector3 s_StartScaleInverse;
|
||||
static Vector3 s_StartCenter;
|
||||
static Vector3 s_Direction;
|
||||
static bool s_SizeManipulationInit;
|
||||
static float s_SizeDelta;
|
||||
|
||||
static float s_DefaultMidpointHandleSize = 0.03f;
|
||||
static float s_DefaultMidpointSquareSize = 0.15f;
|
||||
|
||||
//Orientation Handle Manipulation
|
||||
static float s_CurrentAngle = 0;
|
||||
static int s_CurrentArrowHovered = -1;
|
||||
static Quaternion s_ShapeRotation = Quaternion.identity;
|
||||
static Vector3[][] s_ArrowsLines;
|
||||
|
||||
static Vector3[][] arrowsLines
|
||||
{
|
||||
get
|
||||
{
|
||||
if(s_ArrowsLines == null)
|
||||
{
|
||||
s_ArrowsLines = new Vector3[4][];
|
||||
for (int i = 0; i < s_ArrowsLines.Length; i++)
|
||||
s_ArrowsLines[i] = new Vector3[3];
|
||||
}
|
||||
|
||||
return s_ArrowsLines;
|
||||
}
|
||||
}
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_Faces = null;
|
||||
s_CurrentId = -1;
|
||||
s_SizeManipulationInit = false;
|
||||
s_SizeDelta = 0f;
|
||||
s_StartMousePosition = Vector2.zero;
|
||||
s_StartSize = Vector3.zero;
|
||||
s_StartPositionLocal = Vector3.zero;
|
||||
s_StartPositionGlobal = Vector3.zero;
|
||||
s_StartScale = Vector3.zero;
|
||||
s_StartScaleInverse = Vector3.zero;
|
||||
s_StartCenter = Vector3.zero;
|
||||
s_Direction = Vector3.zero;
|
||||
s_DefaultMidpointHandleSize = 0.03f;
|
||||
s_DefaultMidpointSquareSize = 0.15f;
|
||||
s_CurrentAngle = 0f;
|
||||
s_CurrentArrowHovered = -1;
|
||||
s_ShapeRotation = Quaternion.identity;
|
||||
s_ArrowsLines = null;
|
||||
}
|
||||
|
||||
public override bool gridSnapEnabled => true;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_OverlayTitle = new GUIContent("Shape Settings");
|
||||
m_ShapeEditor = Editor.CreateEditor(targets.ToArray(), typeof(ProBuilderShapeEditor));
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if(m_ShapeEditor != null)
|
||||
DestroyImmediate(m_ShapeEditor);
|
||||
}
|
||||
|
||||
void PlaymodeStateChanged(PlayModeStateChange stateChange)
|
||||
{
|
||||
if(stateChange == PlayModeStateChange.ExitingEditMode
|
||||
|| stateChange == PlayModeStateChange.ExitingPlayMode)
|
||||
return;
|
||||
|
||||
if(m_ShapeEditor != null)
|
||||
DestroyImmediate(m_ShapeEditor);
|
||||
|
||||
m_ShapeEditor = Editor.CreateEditor(targets.ToArray(), typeof(ProBuilderShapeEditor));
|
||||
}
|
||||
|
||||
public override void OnActivated()
|
||||
{
|
||||
base.OnActivated();
|
||||
EditorApplication.playModeStateChanged += PlaymodeStateChanged ;
|
||||
ProBuilderEditor.selectModeChanged += OnSelectModeChanged;
|
||||
ToolManager.activeContextChanged += OnActiveContextChanged;
|
||||
}
|
||||
|
||||
public override void OnWillBeDeactivated()
|
||||
{
|
||||
base.OnWillBeDeactivated();
|
||||
ToolManager.activeContextChanged -= OnActiveContextChanged;
|
||||
ProBuilderEditor.selectModeChanged -= OnSelectModeChanged;
|
||||
EditorApplication.playModeStateChanged -= PlaymodeStateChanged ;
|
||||
}
|
||||
|
||||
void OnSelectModeChanged(SelectMode selectMode)
|
||||
{
|
||||
if(ToolManager.IsActiveTool(this))
|
||||
ToolManager.RestorePreviousTool();
|
||||
}
|
||||
|
||||
void OnActiveContextChanged()
|
||||
{
|
||||
ToolManager.RestorePreviousPersistentTool();
|
||||
}
|
||||
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
// todo refactor overlays to use `Overlay` class
|
||||
#pragma warning disable 618
|
||||
SceneViewOverlay.Window( m_OverlayTitle, OnOverlayGUI, 0, SceneViewOverlay.WindowDisplayOption.OneWindowPerTitle );
|
||||
#pragma warning restore 618
|
||||
|
||||
if(Event.current.type == EventType.MouseMove)
|
||||
{
|
||||
SceneView.RepaintAll();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(var obj in targets)
|
||||
{
|
||||
var shape = obj as ProBuilderShape;
|
||||
|
||||
if (shape != null && shape.isEditable)
|
||||
DoEditingHandles(shape);
|
||||
}
|
||||
}
|
||||
|
||||
void OnOverlayGUI(Object obj, SceneView view)
|
||||
{
|
||||
GUILayout.BeginVertical(GUILayout.MinWidth(DrawShapeTool.k_MinOverlayWidth));
|
||||
( (ProBuilderShapeEditor) m_ShapeEditor ).DrawShapeGUI();
|
||||
( (ProBuilderShapeEditor) m_ShapeEditor ).DrawShapeParametersGUI(null);
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The Editing handles are used to manipulate and resize ProBuilderShapes
|
||||
/// These handles are used in 2 tools : EditShapeTool and DrawShapeTool. In this second tool,
|
||||
/// these handles allow to modified the last created shape.
|
||||
/// </summary>
|
||||
/// <param name="proBuilderShape">The Shape on which to attach the handles</param>
|
||||
internal static void DoEditingHandles(ProBuilderShape proBuilderShape, DrawShapeTool tool = null)
|
||||
{
|
||||
if(proBuilderShape == null)
|
||||
return;
|
||||
|
||||
var matrix = Matrix4x4.TRS(proBuilderShape.shapeWorldCenter, proBuilderShape.transform.rotation, Vector3.one);
|
||||
|
||||
using (new Handles.DrawingScope(matrix))
|
||||
{
|
||||
EditorShapeUtility.UpdateFaces(proBuilderShape.editionBounds, proBuilderShape.transform.lossyScale, faces);
|
||||
|
||||
for (int i = 0; i < 4; ++i)
|
||||
k_OrientationControlIDs[i] = GUIUtility.GetControlID(FocusType.Passive);
|
||||
for (int i = 0; i < faces.Length; ++i)
|
||||
s_FaceControlIDs[i] = GUIUtility.GetControlID(FocusType.Passive);
|
||||
|
||||
if (!(proBuilderShape.shape is Cube))
|
||||
{
|
||||
var absSize = Math.Abs(proBuilderShape.editionBounds.size);
|
||||
if (absSize.x > Mathf.Epsilon && absSize.y > Mathf.Epsilon && absSize.z > Mathf.Epsilon)
|
||||
DoOrientationHandles(proBuilderShape, tool);
|
||||
}
|
||||
|
||||
DoSizeHandles(proBuilderShape, tool);
|
||||
}
|
||||
}
|
||||
|
||||
static void DoSizeHandles(ProBuilderShape proBuilderShape, DrawShapeTool tool = null)
|
||||
{
|
||||
int faceCount = s_Faces.Length;
|
||||
|
||||
var evt = Event.current;
|
||||
|
||||
var is2D = proBuilderShape.shape is Plane || proBuilderShape.shape is Sprite;
|
||||
for(int i = 0; i < faceCount; i++)
|
||||
{
|
||||
var face = faces[i];
|
||||
if(is2D && !face.IsValid)
|
||||
continue;
|
||||
|
||||
if(Event.current.type == EventType.Repaint)
|
||||
{
|
||||
Color color = k_BoundsHandleColor;
|
||||
color.a *= face.IsVisible ? 1f : 0.5f;
|
||||
using(new Handles.DrawingScope(color))
|
||||
{
|
||||
int pointsCount = face.Points.Length;
|
||||
for(int k = 0; k < pointsCount; k++)
|
||||
Handles.DrawLine(face.Points[k], face.Points[( k + 1 ) % pointsCount]);
|
||||
}
|
||||
}
|
||||
|
||||
if( DoFaceSizeHandle(proBuilderShape.transform, face, s_FaceControlIDs[i]) )
|
||||
{
|
||||
|
||||
if(!s_SizeManipulationInit)
|
||||
{
|
||||
s_StartCenter = proBuilderShape.shapeWorldCenter;
|
||||
s_StartScale = proBuilderShape.transform.lossyScale;
|
||||
s_StartScaleInverse = new Vector3(1f / Mathf.Abs(s_StartScale.x), 1f/Mathf.Abs(s_StartScale.y), 1f/Mathf.Abs(s_StartScale.z));
|
||||
s_StartPositionLocal = proBuilderShape.shapeLocalBounds.center + face.CenterPosition;
|
||||
s_StartPositionGlobal = proBuilderShape.transform.TransformPoint(s_StartPositionLocal);
|
||||
s_StartSize = proBuilderShape.size;
|
||||
s_SizeManipulationInit = true;
|
||||
s_Direction = Vector3.Scale(face.Normal, Math.Sign(s_StartSize));
|
||||
}
|
||||
|
||||
var targetSize = s_StartSize;
|
||||
//Should we expand on the 2 sides?
|
||||
var modifier = evt.alt ? 2f : 1f;
|
||||
var delta = modifier * ( s_SizeDelta * s_Faces[i].Normal );
|
||||
|
||||
if(Math.IsCardinalAxis(proBuilderShape.transform.up)
|
||||
&& EditorSnapSettings.gridSnapEnabled
|
||||
&& !EditorSnapSettings.incrementalSnapActive
|
||||
&& !evt.alt)
|
||||
{
|
||||
var facePosition = s_StartPositionGlobal + Vector3.Scale(delta,s_StartScaleInverse);
|
||||
var snapValue = Vector3.Scale(Vector3.Scale(EditorSnapping.activeMoveSnapValue, s_StartScaleInverse), Math.Abs(face.Normal));
|
||||
|
||||
facePosition = ProBuilderSnapping.Snap(facePosition, snapValue);
|
||||
targetSize += Vector3.Scale((facePosition - s_StartPositionGlobal), s_Direction);
|
||||
}
|
||||
else
|
||||
{
|
||||
var snapValue = EditorSnapSettings.incrementalSnapActive
|
||||
? Vector3.Scale(Vector3.Scale(EditorSnapping.activeMoveSnapValue, s_StartScaleInverse), Math.Abs(face.Normal))
|
||||
: Vector3.zero;
|
||||
|
||||
//Move regarding the face tangent direction
|
||||
delta.Scale(s_Direction);
|
||||
//scale by the object scale factor
|
||||
delta.Scale(s_StartScaleInverse);
|
||||
|
||||
targetSize += ProBuilderSnapping.Snap(delta, snapValue);
|
||||
}
|
||||
|
||||
var center = Vector3.zero;
|
||||
if(!evt.alt)
|
||||
{
|
||||
center = Vector3.Scale((targetSize - s_StartSize) / 2f, s_Direction);
|
||||
center = Vector3.Scale(center, Math.Sign(s_StartScale));
|
||||
center = proBuilderShape.transform.TransformVector(center);
|
||||
}
|
||||
ApplyProperties(proBuilderShape, s_StartCenter + center, targetSize);
|
||||
|
||||
if(tool != null)
|
||||
tool.SaveShapeParams(proBuilderShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool DoFaceSizeHandle(Transform trs, FaceData face, int controlID)
|
||||
{
|
||||
if( k_OrientationControlIDs.Contains(HandleUtility.nearestControl) && !EditorShapeUtility.PointerIsInFace(face) )
|
||||
return false;
|
||||
|
||||
Event evt = Event.current;
|
||||
float handleSize = HandleUtility.GetHandleSize(face.CenterPosition) * s_DefaultMidpointHandleSize;
|
||||
bool isSelected = (HandleUtility.nearestControl == controlID && s_CurrentId == -1) || s_CurrentId == controlID;
|
||||
|
||||
switch(evt.GetTypeForControl(controlID))
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if (HandleUtility.nearestControl == controlID && evt.button == 0)
|
||||
{
|
||||
s_CurrentId = controlID;
|
||||
GUIUtility.hotControl = controlID;
|
||||
s_StartMousePosition = evt.mousePosition;
|
||||
s_SizeManipulationInit = false;
|
||||
evt.Use();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
break;
|
||||
case EventType.MouseUp:
|
||||
if (GUIUtility.hotControl == controlID && evt.button == 0)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
evt.Use();
|
||||
s_CurrentId = -1;
|
||||
s_SizeManipulationInit = false;
|
||||
}
|
||||
break;
|
||||
case EventType.Layout:
|
||||
HandleUtility.AddControl(controlID, HandleUtility.DistanceToLine(face.CenterPosition, face.CenterPosition) / 2f);
|
||||
break;
|
||||
case EventType.Repaint:
|
||||
Color color = isSelected ? EditorHandleDrawing.edgeSelectedColor : k_BoundsHandleColor;
|
||||
color.a *= face.IsVisible ? 1f : 0.25f;
|
||||
using(new Handles.DrawingScope(color))
|
||||
Handles.DotHandleCap(controlID, face.CenterPosition , Quaternion.identity, handleSize, EventType.Repaint);
|
||||
break;
|
||||
case EventType.MouseDrag:
|
||||
if(s_CurrentId == controlID)
|
||||
{
|
||||
using(new Handles.DrawingScope(Matrix4x4.identity))
|
||||
s_SizeDelta = HandleUtility.CalcLineTranslation(s_StartMousePosition, evt.mousePosition, s_StartPositionGlobal, trs.TransformDirection(face.Normal).normalized);
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void DoOrientationHandles(ProBuilderShape proBuilderShape, DrawShapeTool tool)
|
||||
{
|
||||
if( GUIUtility.hotControl != 0 && !k_OrientationControlIDs.Contains(GUIUtility.hotControl) )
|
||||
return;
|
||||
|
||||
foreach(var f in faces)
|
||||
{
|
||||
if(f.IsVisible && EditorShapeUtility.PointerIsInFace(f))
|
||||
{
|
||||
if(DoOrientationHandle(f, proBuilderShape))
|
||||
{
|
||||
UndoUtility.RecordComponents<Transform, ProBuilderMesh, ProBuilderShape>(proBuilderShape.GetComponents(typeof(Component)),"Rotate Shape");
|
||||
proBuilderShape.RotateInsideBounds(s_ShapeRotation);
|
||||
|
||||
ProBuilderEditor.Refresh();
|
||||
|
||||
if(tool != null)
|
||||
tool.SaveShapeParams(proBuilderShape);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
static bool DoOrientationHandle(FaceData face, ProBuilderShape proBuilderShape)
|
||||
{
|
||||
Event evt = Event.current;
|
||||
bool hasRotated = false;
|
||||
|
||||
switch(evt.type)
|
||||
{
|
||||
case EventType.MouseDown:
|
||||
if ( k_OrientationControlIDs.Contains(HandleUtility.nearestControl) && evt.button == 0 )
|
||||
{
|
||||
s_CurrentId = HandleUtility.nearestControl;
|
||||
GUIUtility.hotControl = s_CurrentId;
|
||||
evt.Use();
|
||||
}
|
||||
break;
|
||||
case EventType.MouseUp:
|
||||
if (k_OrientationControlIDs.Contains(HandleUtility.nearestControl) && evt.button == 0 )
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
evt.Use();
|
||||
if(s_CurrentId == HandleUtility.nearestControl)
|
||||
{
|
||||
//Execute rotation
|
||||
Vector3 targetedNormal = Vector3.zero;
|
||||
for(int i = 0; i < k_OrientationControlIDs.Length; i++)
|
||||
{
|
||||
if(k_OrientationControlIDs[i] == s_CurrentId)
|
||||
{
|
||||
targetedNormal = (arrowsLines[i][1] - face.CenterPosition).normalized;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var currentNormal = face.Normal;
|
||||
currentNormal.Scale(Math.Sign(proBuilderShape.size));
|
||||
targetedNormal.Scale(Math.Sign(proBuilderShape.size));
|
||||
Vector3 rotationAxis = Vector3.Cross(currentNormal,targetedNormal);
|
||||
var angle = Vector3.SignedAngle(currentNormal, targetedNormal, rotationAxis);
|
||||
s_ShapeRotation = Quaternion.AngleAxis(angle, rotationAxis);
|
||||
s_CurrentAngle = (s_CurrentAngle + angle) % 360;
|
||||
|
||||
hasRotated = true;
|
||||
}
|
||||
s_CurrentId = -1;
|
||||
}
|
||||
break;
|
||||
case EventType.Layout:
|
||||
for(int i = 0; i < 4; i++)
|
||||
{
|
||||
var rectPos = 0.8f * arrowsLines[i][1] + 0.2f * face.CenterPosition;
|
||||
float dist = HandleUtility.DistanceToRectangle( rectPos,
|
||||
Quaternion.LookRotation(face.Normal),
|
||||
HandleUtility.GetHandleSize(face.CenterPosition) * s_DefaultMidpointSquareSize/2f);
|
||||
HandleUtility.AddControl(k_OrientationControlIDs[i], dist);
|
||||
}
|
||||
break;
|
||||
case EventType.Repaint:
|
||||
if(s_CurrentArrowHovered != HandleUtility.nearestControl)
|
||||
s_CurrentAngle = 0f;
|
||||
|
||||
int pointsCount = face.Points.Length;
|
||||
s_CurrentArrowHovered = -1;
|
||||
for(int i = 0; i < pointsCount; i++)
|
||||
{
|
||||
var rectHandleSize = HandleUtility.GetHandleSize(face.CenterPosition) * s_DefaultMidpointSquareSize;
|
||||
|
||||
var sideDirection = ( face.Points[( i + 1 ) % pointsCount] - face.Points[i] ).normalized;
|
||||
var arrowDirection = Vector3.Cross(face.Normal.normalized, sideDirection).normalized;
|
||||
|
||||
var topDirection = 2.5f * rectHandleSize * arrowDirection;
|
||||
var top = face.CenterPosition + topDirection;
|
||||
var A = topDirection.magnitude;
|
||||
var a = 0.33f * Mathf.Sqrt(2f * A * A);
|
||||
var h = 0.5f * Mathf.Sqrt(2f * a * a);
|
||||
arrowsLines[i][0] = top - ( h * arrowDirection + h * sideDirection );
|
||||
arrowsLines[i][1] = top;
|
||||
arrowsLines[i][2] = top - ( h * arrowDirection - h * sideDirection );
|
||||
|
||||
bool selected = HandleUtility.nearestControl == k_OrientationControlIDs[i];
|
||||
|
||||
Color color = selected
|
||||
? EditorHandleDrawing.edgeSelectedColor
|
||||
: k_BoundsHandleColor;
|
||||
color.a = 1.0f;
|
||||
|
||||
using(new Handles.DrawingScope(color))
|
||||
{
|
||||
Handles.DrawAAPolyLine(5f, arrowsLines[i]);
|
||||
if(selected)
|
||||
{
|
||||
EditorGUIUtility.AddCursorRect(new Rect(0,0,Screen.width, Screen.height), MouseCursor.RotateArrow);
|
||||
s_CurrentArrowHovered = HandleUtility.nearestControl;
|
||||
Handles.DrawAAPolyLine(3f,
|
||||
new Vector3[]
|
||||
{
|
||||
Vector3.Scale(proBuilderShape.shapeRotation * Vector3.up, proBuilderShape.size / 2f),
|
||||
Vector3.zero,
|
||||
Vector3.Scale(proBuilderShape.shapeRotation * Vector3.forward, proBuilderShape.size / 2f)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case EventType.MouseDrag:
|
||||
if(k_OrientationControlIDs.Contains(s_CurrentId) && HandleUtility.nearestControl != s_CurrentId)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
s_CurrentId = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return hasRotated;
|
||||
}
|
||||
|
||||
static void ApplyProperties(ProBuilderShape proBuilderShape, Vector3 newCenterPosition, Vector3 newSize)
|
||||
{
|
||||
var bounds = new Bounds();
|
||||
bounds.center = newCenterPosition;
|
||||
bounds.size = newSize;
|
||||
|
||||
UndoUtility.RecordComponents<Transform, ProBuilderMesh, ProBuilderShape>(proBuilderShape.GetComponents(typeof(Component)),"Resize Shape");
|
||||
proBuilderShape.UpdateBounds(bounds);
|
||||
|
||||
ProBuilderEditor.Refresh(false);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99a28d28a160b4da78802caf2a3484a0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Where the sceneview toolbar sits in relation the scene view rect.
|
||||
/// </summary>
|
||||
enum SceneToolbarLocation
|
||||
{
|
||||
UpperCenter,
|
||||
UpperLeft,
|
||||
UpperRight,
|
||||
BottomCenter,
|
||||
BottomLeft,
|
||||
BottomRight
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// How should Unity represent selected objects?
|
||||
/// </summary>
|
||||
/// <remarks>Editor only.</remarks>
|
||||
[System.Flags]
|
||||
enum SelectionRenderState
|
||||
{
|
||||
None = 0x0,
|
||||
Wireframe = 0x1,
|
||||
Outline = 0x2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f90915a295ab524097b6e004b64d015
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,273 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.ProBuilder.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Additional GUI functions for Editor use.
|
||||
/// </summary>
|
||||
static class EditorGUILayout
|
||||
{
|
||||
static bool s_RowToggle = true;
|
||||
static readonly Color s_RowOddColor = new Color(.45f, .45f, .45f, .2f);
|
||||
static readonly Color s_RowEvenColor = new Color(.30f, .30f, .30f, .2f);
|
||||
|
||||
public static void BeginRow(int index = -1)
|
||||
{
|
||||
if (index > -1)
|
||||
s_RowToggle = index % 2 == 0;
|
||||
|
||||
EditorGUIUtility.PushBackgroundColor(s_RowToggle ? s_RowEvenColor : s_RowOddColor);
|
||||
GUILayout.BeginHorizontal(EditorStyles.rowStyle);
|
||||
s_RowToggle = !s_RowToggle;
|
||||
EditorGUIUtility.PopBackgroundColor();
|
||||
}
|
||||
|
||||
public static void EndRow()
|
||||
{
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An automatically laid out toolbar that returns the index of the selected button. Optionally allows no selection.
|
||||
/// </summary>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="content"></param>
|
||||
/// <param name="style"></param>
|
||||
/// <param name="allowNoSelection"></param>
|
||||
/// <param name="addlParams"></param>
|
||||
/// <returns></returns>
|
||||
public static int Toolbar(int index, GUIContent[] content, GUIStyle style, bool allowNoSelection = false, params GUILayoutOption[] addlParams)
|
||||
{
|
||||
return Toolbar(index, content, style, style, style, allowNoSelection, addlParams);
|
||||
}
|
||||
|
||||
public static int Toolbar(int index, GUIContent[] content, GUIStyle left, GUIStyle mid, GUIStyle right, bool allowNoSelection = false, params GUILayoutOption[] addlParams)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
for (int i = 0; i < content.Length; i++)
|
||||
{
|
||||
GUIStyle m_Style = i < 1 ? left : (i >= content.Length - 1 ? right : mid);
|
||||
|
||||
if (index == i)
|
||||
m_Style = EditorGUIUtility.GetOnStyle(m_Style);
|
||||
|
||||
if (GUILayout.Button(content[i], m_Style, addlParams))
|
||||
{
|
||||
if (index == i && allowNoSelection)
|
||||
index = -1;
|
||||
else
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/**
|
||||
* An automatically laid out toolbar that toggles flags. Content corresponds to the bits starting at 1 - ex:
|
||||
* - content[0] = 0x1
|
||||
* - content[1] = 0x2
|
||||
* - content[2] = 0x4
|
||||
*/
|
||||
public static int FlagToolbar(int index, GUIContent[] content, GUIStyle style, bool allowNoSelection = false, bool allowMultipleSelected = true, params GUILayoutOption[] addlParams)
|
||||
{
|
||||
return FlagToolbar(index, content, style, style, style, allowNoSelection, allowMultipleSelected, addlParams);
|
||||
}
|
||||
|
||||
public static int FlagToolbar(int index, GUIContent[] content, bool allowNoSelection = false, bool allowMultipleSelected = true, params GUILayoutOption[] addlParams)
|
||||
{
|
||||
return FlagToolbar(index, content, UnityEditor.EditorStyles.miniButtonLeft, UnityEditor.EditorStyles.miniButtonMid, UnityEditor.EditorStyles.miniButtonRight, allowNoSelection, allowMultipleSelected, addlParams);
|
||||
}
|
||||
|
||||
public static int FlagToolbar(int index, GUIContent[] content, GUIStyle left, GUIStyle mid, GUIStyle right, bool allowNoSelection = false, bool allowMultipleSelected = true, params GUILayoutOption[] addlParams)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
for (int i = 0; i < content.Length; i++)
|
||||
{
|
||||
GUIStyle m_Style = i < 1 ? left : (i >= content.Length - 1 ? right : mid);
|
||||
|
||||
if ((index & (0x1 << i)) > 0)
|
||||
m_Style = EditorGUIUtility.GetOnStyle(m_Style);
|
||||
|
||||
if (GUILayout.Button(content[i], m_Style, addlParams))
|
||||
{
|
||||
if (!allowMultipleSelected)
|
||||
index = (index & (0x1 << i));
|
||||
|
||||
index ^= (0x1 << i);
|
||||
|
||||
if (!allowNoSelection && index == 0x0)
|
||||
index = 0x1 << i;
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
class ResizeHandleState
|
||||
{
|
||||
public Vector2 origin;
|
||||
public Rect startingRect;
|
||||
}
|
||||
|
||||
delegate Rect ComputeResize(Rect currentRect, ResizeHandleState handleState, Event currentEvent, int minimumWidth, int minimumHeight);
|
||||
|
||||
static KeyValuePair<int, Tuple<Rect, ComputeResize>> CreateResizeHandleControl(Rect activeRect, String suffix, Rect parentWindowRect, UnityEditor.MouseCursor cursor, ComputeResize resizeDelegate)
|
||||
{
|
||||
int id = GUIUtility.GetControlID(("ProBuilderWindowResize" + suffix).GetHashCode(), FocusType.Passive, parentWindowRect);
|
||||
HandleUtility.AddControl(id, Vector2.Distance(activeRect.center, Event.current.mousePosition));
|
||||
UnityEditor.EditorGUIUtility.AddCursorRect(activeRect, cursor);
|
||||
return new KeyValuePair<int, Tuple<Rect, ComputeResize>>(id, new Tuple<Rect, ComputeResize>(activeRect, resizeDelegate));
|
||||
}
|
||||
|
||||
static Rect ResizeBottomRight(Rect currentRect, ResizeHandleState handleState, Event currentEvent, int minimumWidth, int minimumHeight)
|
||||
{
|
||||
currentRect.width = handleState.startingRect.width + (currentEvent.mousePosition.x - handleState.origin.x);
|
||||
currentRect.height = handleState.startingRect.height + (currentEvent.mousePosition.y - handleState.origin.y);
|
||||
return currentRect;
|
||||
}
|
||||
|
||||
static Rect ResizeBottomLeft(Rect currentRect, ResizeHandleState handleState, Event currentEvent, int minimumWidth, int minimumHeight)
|
||||
{
|
||||
currentRect.width = currentRect.width - (currentEvent.mousePosition.x - handleState.origin.x);
|
||||
currentRect.width = Mathf.Max(currentRect.width, minimumWidth);
|
||||
currentRect.x = handleState.startingRect.xMax - currentRect.width;
|
||||
currentRect.height = handleState.startingRect.height + (currentEvent.mousePosition.y - handleState.origin.y);
|
||||
return currentRect;
|
||||
}
|
||||
|
||||
static Rect ResizeBottom(Rect currentRect, ResizeHandleState handleState, Event currentEvent, int minimumWidth, int minimumHeight)
|
||||
{
|
||||
currentRect.height = handleState.startingRect.height + (currentEvent.mousePosition.y - handleState.origin.y);
|
||||
return currentRect;
|
||||
}
|
||||
|
||||
static Rect ResizeTop(Rect currentRect, ResizeHandleState handleState, Event currentEvent, int minimumWidth, int minimumHeight)
|
||||
{
|
||||
currentRect.height = currentRect.height - (currentEvent.mousePosition.y - handleState.origin.y);
|
||||
currentRect.height = Mathf.Max(currentRect.height, minimumHeight);
|
||||
currentRect.y = handleState.startingRect.yMax - currentRect.height;
|
||||
return currentRect;
|
||||
}
|
||||
|
||||
static Rect ResizeLeft(Rect currentRect, ResizeHandleState handleState, Event currentEvent, int minimumWidth, int minimumHeight)
|
||||
{
|
||||
currentRect.width = currentRect.width - (currentEvent.mousePosition.x - handleState.origin.x);
|
||||
currentRect.width = Mathf.Max(currentRect.width, minimumWidth);
|
||||
currentRect.x = handleState.startingRect.xMax - currentRect.width;
|
||||
return currentRect;
|
||||
}
|
||||
|
||||
static Rect ResizeRight(Rect currentRect, ResizeHandleState handleState, Event currentEvent, int minimumWidth, int minimumHeight)
|
||||
{
|
||||
currentRect.width = handleState.startingRect.width + (currentEvent.mousePosition.x - handleState.origin.x);
|
||||
return currentRect;
|
||||
}
|
||||
|
||||
static Rect ResizeTopLeft(Rect currentRect, ResizeHandleState handleState, Event currentEvent, int minimumWidth, int minimumHeight)
|
||||
{
|
||||
currentRect.height = currentRect.height - (currentEvent.mousePosition.y - handleState.origin.y);
|
||||
currentRect.height = Mathf.Max(currentRect.height, minimumHeight);
|
||||
currentRect.y = handleState.startingRect.yMax - currentRect.height;
|
||||
currentRect.width = currentRect.width - (currentEvent.mousePosition.x - handleState.origin.x);
|
||||
currentRect.width = Mathf.Max(currentRect.width, minimumWidth);
|
||||
currentRect.x = handleState.startingRect.xMax - currentRect.width;
|
||||
return currentRect;
|
||||
}
|
||||
|
||||
static Rect ResizeTopRight(Rect currentRect, ResizeHandleState handleState, Event currentEvent, int minimumWidth, int minimumHeight)
|
||||
{
|
||||
currentRect.height = currentRect.height - (currentEvent.mousePosition.y - handleState.origin.y);
|
||||
currentRect.height = Mathf.Max(currentRect.height, minimumHeight);
|
||||
currentRect.y = handleState.startingRect.yMax - currentRect.height;
|
||||
currentRect.width = handleState.startingRect.width + (currentEvent.mousePosition.x - handleState.origin.x);
|
||||
return currentRect;
|
||||
}
|
||||
|
||||
static int s_ResizeHandleAreaDimension = 6;
|
||||
static int s_MoveWindowAreaHeight = 30;
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_RowToggle = true;
|
||||
s_ResizeHandleAreaDimension = 6;
|
||||
s_MoveWindowAreaHeight = 30;
|
||||
}
|
||||
|
||||
public static Rect DoResizeHandle(Rect rect, int minimumWidth, int minimumHeight)
|
||||
{
|
||||
var evt = Event.current;
|
||||
if (evt.type == EventType.Used)
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
IDictionary<int, Tuple<Rect, ComputeResize>> resizeHandles = new Dictionary<int, Tuple<Rect, ComputeResize>>();
|
||||
resizeHandles.Add(CreateResizeHandleControl(new Rect(rect.width - s_ResizeHandleAreaDimension, rect.height - s_ResizeHandleAreaDimension, s_ResizeHandleAreaDimension, s_ResizeHandleAreaDimension), "BottomRight", rect, MouseCursor.ResizeUpLeft, ResizeBottomRight));
|
||||
resizeHandles.Add(CreateResizeHandleControl(new Rect(0, rect.height - s_ResizeHandleAreaDimension, s_ResizeHandleAreaDimension, s_ResizeHandleAreaDimension), "BottomLeft", rect, MouseCursor.ResizeUpRight, ResizeBottomLeft));
|
||||
resizeHandles.Add(CreateResizeHandleControl(new Rect(s_ResizeHandleAreaDimension, rect.height - s_ResizeHandleAreaDimension, rect.width - 2*s_ResizeHandleAreaDimension, s_ResizeHandleAreaDimension), "Bottom", rect, MouseCursor.ResizeVertical, ResizeBottom));
|
||||
resizeHandles.Add(CreateResizeHandleControl(new Rect(s_ResizeHandleAreaDimension, 0, rect.width - 2* s_ResizeHandleAreaDimension, s_ResizeHandleAreaDimension), "Top", rect, MouseCursor.ResizeVertical, ResizeTop));
|
||||
resizeHandles.Add(CreateResizeHandleControl(new Rect(0, s_MoveWindowAreaHeight, s_ResizeHandleAreaDimension, rect.height - (s_MoveWindowAreaHeight + s_ResizeHandleAreaDimension)), "Left", rect, MouseCursor.ResizeHorizontal, ResizeLeft));
|
||||
resizeHandles.Add(CreateResizeHandleControl(new Rect(rect.width - s_ResizeHandleAreaDimension, s_MoveWindowAreaHeight, s_ResizeHandleAreaDimension, rect.height - (s_MoveWindowAreaHeight + s_ResizeHandleAreaDimension)), "Right", rect, MouseCursor.ResizeHorizontal, ResizeRight));
|
||||
resizeHandles.Add(CreateResizeHandleControl(new Rect(0, 0, s_ResizeHandleAreaDimension, s_ResizeHandleAreaDimension), "TopLeft", rect, MouseCursor.ResizeUpLeft, ResizeTopLeft));
|
||||
resizeHandles.Add(CreateResizeHandleControl(new Rect(rect.width - s_ResizeHandleAreaDimension, 0, s_ResizeHandleAreaDimension, s_ResizeHandleAreaDimension), "TopRight", rect, MouseCursor.ResizeUpRight, ResizeTopRight));
|
||||
|
||||
if (evt.type == EventType.MouseDown && evt.button == 0)
|
||||
{
|
||||
bool initializedControl = false;
|
||||
foreach (KeyValuePair<int, Tuple<Rect,ComputeResize>> kvp in resizeHandles)
|
||||
{
|
||||
if (kvp.Value.Item1.Contains(evt.mousePosition))
|
||||
{
|
||||
GUIUtility.hotControl = kvp.Key;
|
||||
initializedControl = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!initializedControl)
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
GUI.changed = true;
|
||||
var state = (ResizeHandleState)GUIUtility.GetStateObject(typeof(ResizeHandleState), GUIUtility.hotControl);
|
||||
state.origin = evt.mousePosition;
|
||||
state.startingRect = rect;
|
||||
evt.Use();
|
||||
}
|
||||
else if (!resizeHandles.ContainsKey(GUIUtility.hotControl))
|
||||
{
|
||||
return rect;
|
||||
}
|
||||
if (evt.type == EventType.MouseUp)
|
||||
{
|
||||
GUIUtility.hotControl = 0;
|
||||
GUI.changed = true;
|
||||
evt.Use();
|
||||
}
|
||||
else if (evt.type == EventType.MouseDrag)
|
||||
{
|
||||
var state = (ResizeHandleState)GUIUtility.GetStateObject(typeof(ResizeHandleState), GUIUtility.hotControl);
|
||||
rect = resizeHandles[GUIUtility.hotControl].Item2(rect, state, evt, minimumWidth, minimumHeight);
|
||||
|
||||
GUI.changed = true;
|
||||
evt.Use();
|
||||
}
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public static Rect GetSliderRect(bool hasLabel, params GUILayoutOption[] options)
|
||||
{
|
||||
return UnityEditor.EditorGUILayout.GetSliderRect(hasLabel, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4891ddee861208843a4495ed6e404a57
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,559 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Generic GUI utility methods used in ProBuilder windows.
|
||||
/// </summary>
|
||||
static class EditorGUIUtility
|
||||
{
|
||||
internal static class Styles
|
||||
{
|
||||
private static GUIStyle s_Command;
|
||||
public static GUIStyle command => s_Command ??= "command";
|
||||
public static GUIContent[] selectModeIcons;
|
||||
|
||||
static Texture2D s_ObjectIcon;
|
||||
public static Texture2D ObjectIcon => s_ObjectIcon ??= IconUtility.GetIcon("Modes/Mode_Object");
|
||||
|
||||
static Texture2D s_VertexIcon;
|
||||
public static Texture2D VertexIcon => s_VertexIcon ??= IconUtility.GetIcon("Modes/Mode_Vertex");
|
||||
|
||||
static Texture2D s_EdgeIcon;
|
||||
public static Texture2D EdgeIcon => s_EdgeIcon ??= IconUtility.GetIcon("Modes/Mode_Edge");
|
||||
|
||||
static Texture2D s_FaceIcon;
|
||||
public static Texture2D FaceIcon => s_FaceIcon ??= IconUtility.GetIcon("Modes/Mode_Face");
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
selectModeIcons = new GUIContent[]
|
||||
{
|
||||
ObjectIcon != null
|
||||
? new GUIContent(s_ObjectIcon, "Object Selection")
|
||||
: new GUIContent("OBJ", "Object Selection"),
|
||||
VertexIcon != null
|
||||
? new GUIContent(s_VertexIcon, "Vertex Selection")
|
||||
: new GUIContent("VRT", "Vertex Selection"),
|
||||
EdgeIcon != null
|
||||
? new GUIContent(s_EdgeIcon, "Edge Selection")
|
||||
: new GUIContent("EDG", "Edge Selection"),
|
||||
FaceIcon != null
|
||||
? new GUIContent(s_FaceIcon, "Face Selection")
|
||||
: new GUIContent("FCE", "Face Selection"),
|
||||
};
|
||||
}
|
||||
|
||||
internal static void ResetForPlayMode()
|
||||
{
|
||||
selectModeIcons = null;
|
||||
s_ObjectIcon = null;
|
||||
s_VertexIcon = null;
|
||||
s_EdgeIcon = null;
|
||||
s_FaceIcon = null;
|
||||
}
|
||||
}
|
||||
|
||||
static readonly Color TOOL_SETTINGS_COLOR = UnityEditor.EditorGUIUtility.isProSkin
|
||||
? Color.green
|
||||
: new Color(.2f, .2f, .2f, .2f);
|
||||
|
||||
static GUIStyle _splitStyle;
|
||||
static GUIStyle SplitStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_splitStyle == null)
|
||||
{
|
||||
_splitStyle = new GUIStyle();
|
||||
_splitStyle.normal.background = UnityEditor.EditorGUIUtility.whiteTexture;
|
||||
_splitStyle.margin = new RectOffset(6, 6, 0, 0);
|
||||
}
|
||||
return _splitStyle;
|
||||
}
|
||||
}
|
||||
|
||||
static GUIStyle _centeredGreyMiniLabel;
|
||||
public static GUIStyle CenteredGreyMiniLabel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_centeredGreyMiniLabel == null)
|
||||
{
|
||||
_centeredGreyMiniLabel = new GUIStyle(UnityEditor.EditorStyles.label);
|
||||
_centeredGreyMiniLabel.normal.textColor = Color.gray;
|
||||
_centeredGreyMiniLabel.alignment = TextAnchor.MiddleCenter;
|
||||
}
|
||||
return _centeredGreyMiniLabel;
|
||||
}
|
||||
}
|
||||
|
||||
static GUIStyle _solidBackgroundStyle;
|
||||
public static GUIStyle solidBackgroundStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_solidBackgroundStyle == null)
|
||||
{
|
||||
_solidBackgroundStyle = new GUIStyle();
|
||||
_solidBackgroundStyle.normal.background = UnityEditor.EditorGUIUtility.whiteTexture;
|
||||
}
|
||||
return _solidBackgroundStyle;
|
||||
}
|
||||
}
|
||||
|
||||
static GUIStyle _buttonNoBackgroundSmallMarginStyle = null;
|
||||
public static GUIStyle ButtonNoBackgroundSmallMarginStyle
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_buttonNoBackgroundSmallMarginStyle == null)
|
||||
{
|
||||
_buttonNoBackgroundSmallMarginStyle = new GUIStyle();
|
||||
_buttonNoBackgroundSmallMarginStyle.margin = new RectOffset(0, 0, 0, 0);
|
||||
_buttonNoBackgroundSmallMarginStyle.alignment = TextAnchor.MiddleCenter;
|
||||
_buttonNoBackgroundSmallMarginStyle.padding = new RectOffset(2, 2, 2, 2);
|
||||
}
|
||||
return _buttonNoBackgroundSmallMarginStyle;
|
||||
}
|
||||
}
|
||||
|
||||
static GUIContent _guiContent = null;
|
||||
|
||||
public static GUIContent TempContent(string label, string tooltip = null, Texture2D icon = null)
|
||||
{
|
||||
if (_guiContent == null)
|
||||
_guiContent = new GUIContent();
|
||||
|
||||
_guiContent.text = label;
|
||||
_guiContent.tooltip = tooltip;
|
||||
_guiContent.image = icon;
|
||||
|
||||
return _guiContent;
|
||||
}
|
||||
|
||||
static Stack<bool> s_GuiEnabled = new Stack<bool>();
|
||||
static Stack<Color> s_ContentColor = new Stack<Color>();
|
||||
static Stack<Color> s_BackgroundColor = new Stack<Color>();
|
||||
|
||||
public static void PushGUIEnabled(bool enabled)
|
||||
{
|
||||
s_GuiEnabled.Push(GUI.enabled);
|
||||
GUI.enabled = enabled;
|
||||
}
|
||||
|
||||
public static void PopGUIEnabled()
|
||||
{
|
||||
GUI.enabled = s_GuiEnabled.Pop();
|
||||
}
|
||||
|
||||
public static void PushGUIContentColor(Color color)
|
||||
{
|
||||
s_ContentColor.Push(GUI.color);
|
||||
GUI.contentColor = color;
|
||||
}
|
||||
|
||||
public static void PopGUIContentColor()
|
||||
{
|
||||
GUI.contentColor = s_ContentColor.Pop();
|
||||
}
|
||||
|
||||
public static void PushBackgroundColor(Color color)
|
||||
{
|
||||
s_BackgroundColor.Push(GUI.backgroundColor);
|
||||
GUI.backgroundColor = color;
|
||||
}
|
||||
|
||||
public static void PopBackgroundColor()
|
||||
{
|
||||
GUI.backgroundColor = s_BackgroundColor.Pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a horizontal line and inserts a GUILayout.Space(2).
|
||||
* @param lines How many lines to draw. Typically 1 or 2 suffice.
|
||||
*/
|
||||
public static void DrawSeparator(int lines, Color color)
|
||||
{
|
||||
Color old = UnityEngine.GUI.backgroundColor;
|
||||
UnityEngine.GUI.backgroundColor = color;
|
||||
DrawSeparator(lines);
|
||||
UnityEngine.GUI.backgroundColor = old;
|
||||
}
|
||||
|
||||
public static void DrawSeparator(int lines)
|
||||
{
|
||||
GUILayout.Box("", SplitStyle, GUILayout.MaxHeight(2));
|
||||
|
||||
for (int i = 1; i < lines; i++)
|
||||
{
|
||||
GUILayout.Space(2);
|
||||
GUILayout.Box("", SplitStyle, GUILayout.MaxHeight(2));
|
||||
}
|
||||
}
|
||||
|
||||
static Dictionary<GUIStyle, GUIStyle> onStyles = new Dictionary<GUIStyle, GUIStyle>();
|
||||
|
||||
public static GUIStyle GetOnStyle(GUIStyle style)
|
||||
{
|
||||
GUIStyle on;
|
||||
|
||||
if (onStyles.TryGetValue(style, out on))
|
||||
return on;
|
||||
|
||||
on = new GUIStyle(style);
|
||||
on.normal.textColor = on.onNormal.textColor;
|
||||
on.normal.background = on.onNormal.background;
|
||||
onStyles.Add(style, on);
|
||||
return on;
|
||||
}
|
||||
|
||||
static Dictionary<GUIStyle, GUIStyle> activeStyles = new Dictionary<GUIStyle, GUIStyle>();
|
||||
|
||||
public static GUIStyle GetActiveStyle(GUIStyle style)
|
||||
{
|
||||
GUIStyle activeStyle;
|
||||
|
||||
if (activeStyles.TryGetValue(style, out activeStyle))
|
||||
return activeStyle;
|
||||
|
||||
activeStyle = new GUIStyle(style);
|
||||
activeStyle.normal.textColor = activeStyle.active.textColor;
|
||||
activeStyle.normal.background = activeStyle.active.background;
|
||||
activeStyles.Add(style, activeStyle);
|
||||
return activeStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a solid color block at rect.
|
||||
*/
|
||||
public static void DrawSolidColor(Rect rect, Color col)
|
||||
{
|
||||
Color old = UnityEngine.GUI.backgroundColor;
|
||||
UnityEngine.GUI.backgroundColor = col;
|
||||
|
||||
UnityEngine.GUI.Box(rect, "", SplitStyle);
|
||||
|
||||
UnityEngine.GUI.backgroundColor = old;
|
||||
}
|
||||
|
||||
const int FieldBoxWidth = 64;
|
||||
|
||||
public static float FloatFieldConstrained(GUIContent content, float value, int width)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(content, GUILayout.MaxWidth(width - FieldBoxWidth));
|
||||
value = UnityEditor.EditorGUILayout.FloatField("", value, GUILayout.MaxWidth(FieldBoxWidth - 4));
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int IntFieldConstrained(GUIContent content, int value, int width)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label(content, GUILayout.MaxWidth(width - FieldBoxWidth));
|
||||
value = UnityEditor.EditorGUILayout.IntField("", value, GUILayout.MaxWidth(FieldBoxWidth - 4));
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static GUIContent slider_guicontent = new GUIContent("", "");
|
||||
|
||||
public static float FreeSlider(string content, float value, float min, float max)
|
||||
{
|
||||
slider_guicontent.text = content;
|
||||
return FreeSlider(slider_guicontent, value, min, max);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to EditorGUILayoutUtility.Slider, except this allows for values outside of the min/max bounds via the float field.
|
||||
*/
|
||||
public static float FreeSlider(GUIContent content, float value, float min, float max)
|
||||
{
|
||||
float pixelsPerPoint = 1f;
|
||||
|
||||
pixelsPerPoint = UnityEditor.EditorGUIUtility.pixelsPerPoint;
|
||||
|
||||
float PAD = 8f / pixelsPerPoint;
|
||||
float sliderHeight = UnityEditor.EditorGUIUtility.singleLineHeight;
|
||||
const float MIN_LABEL_WIDTH = 0f;
|
||||
const float MAX_LABEL_WIDTH = 128f;
|
||||
const float MIN_FIELD_WIDTH = 48f;
|
||||
|
||||
Rect rect = EditorGUILayout.GetSliderRect(true);
|
||||
float y = rect.y;
|
||||
|
||||
float labelWidth = content != null ? Mathf.Max(MIN_LABEL_WIDTH, Mathf.Min(GUI.skin.label.CalcSize(content).x + PAD, MAX_LABEL_WIDTH)) : 0f;
|
||||
float remaining = ((Screen.width / pixelsPerPoint) - (PAD * 2f)) - labelWidth;
|
||||
float sliderWidth = remaining - (MIN_FIELD_WIDTH + PAD);
|
||||
float floatWidth = MIN_FIELD_WIDTH;
|
||||
|
||||
Rect labelRect = new Rect(PAD, y + 2f, labelWidth, sliderHeight);
|
||||
Rect sliderRect = new Rect(labelRect.x + labelWidth, y + 1f, sliderWidth, sliderHeight);
|
||||
Rect floatRect = new Rect(sliderRect.x + sliderRect.width + PAD, y + 1f, floatWidth, sliderHeight);
|
||||
|
||||
if (content != null)
|
||||
GUI.Label(labelRect, content);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
int controlID = GUIUtility.GetControlID(FocusType.Passive, sliderRect);
|
||||
float tmp = value;
|
||||
tmp = GUI.Slider(sliderRect, tmp, 0f, min, max, GUI.skin.horizontalSlider, (!EditorGUI.showMixedValue) ? GUI.skin.horizontalSliderThumb : "SliderMixed", true, controlID);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
value = Event.current.control ? 1f * Mathf.Round(tmp / 1f) : tmp;
|
||||
|
||||
value = EditorGUI.FloatField(floatRect, value);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
public static int FreeSliderWithRange(string content, int value, int min, int max, ref int uiMin, ref int uiMax, ref bool expanded)
|
||||
{
|
||||
slider_guicontent.text = content;
|
||||
return FreeSliderWithRange(slider_guicontent, value, min, max, ref uiMin, ref uiMax, ref expanded);
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to EditorGUILayoutUtility.Slider, except this allows for values outside of the uiMin/uiMax bounds via the int field.
|
||||
* Contrary to the FreeSlider however it has a hard range defined with min and max.
|
||||
*/
|
||||
public static int FreeSliderWithRange(GUIContent content, int value, int min, int max, ref int uiMin, ref int uiMax, ref bool expanded)
|
||||
{
|
||||
float pixelsPerPoint = 1f;
|
||||
|
||||
pixelsPerPoint = UnityEditor.EditorGUIUtility.pixelsPerPoint;
|
||||
|
||||
float PAD = 6f / pixelsPerPoint;
|
||||
const float SLIDER_HEIGHT = 16f;
|
||||
const float MIN_LABEL_WIDTH = 0f;
|
||||
const float MAX_LABEL_WIDTH = 128f;
|
||||
const float MIN_FIELD_WIDTH = 48f;
|
||||
|
||||
GUILayoutUtility.GetRect(UnityEditor.EditorGUIUtility.currentViewWidth / pixelsPerPoint, 18);
|
||||
|
||||
Rect previousRect = GUILayoutUtility.GetLastRect();
|
||||
float y = previousRect.y;
|
||||
|
||||
float labelWidth = content != null ? Mathf.Max(MIN_LABEL_WIDTH, Mathf.Min(GUI.skin.label.CalcSize(content).x + PAD, MAX_LABEL_WIDTH)) : 0f;
|
||||
float remaining = ((Screen.width / pixelsPerPoint) - (PAD * 4f)) - labelWidth;
|
||||
float sliderWidth = remaining - (MIN_FIELD_WIDTH + PAD);
|
||||
float intWidth = MIN_FIELD_WIDTH;
|
||||
float indentOffset = EditorGUI.indentLevel * 15f;
|
||||
|
||||
Rect labelRect = new Rect(PAD, y + 2f, labelWidth, SLIDER_HEIGHT);
|
||||
Rect sliderRect = new Rect(labelRect.x + labelWidth + 2*PAD, y + 1f, sliderWidth, SLIDER_HEIGHT);
|
||||
Rect intRect = new Rect(sliderRect.x + sliderRect.width + PAD, y + 1f, intWidth, SLIDER_HEIGHT);
|
||||
|
||||
Rect totalRect = GUILayoutUtility.GetRect(1, UnityEditor.EditorGUIUtility.singleLineHeight);
|
||||
Rect foldoutRect = new Rect(labelRect.xMax - PAD, labelRect.y, 15, totalRect.height);
|
||||
|
||||
if (content != null)
|
||||
GUI.Label(labelRect, content);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
int controlID = GUIUtility.GetControlID(FocusType.Passive, sliderRect);
|
||||
float tmp = value;
|
||||
float tmpUIMin = uiMin;
|
||||
float tmpUIMax = uiMax;
|
||||
tmp = GUI.Slider(sliderRect, tmp, 0f, tmpUIMin, tmpUIMax, GUI.skin.horizontalSlider, (!EditorGUI.showMixedValue) ? GUI.skin.horizontalSliderThumb : "SliderMixed", true, controlID);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
value = (int) (Event.current.control ? 1 * Mathf.Round(tmp / 1f) : tmp);
|
||||
|
||||
value = EditorGUI.DelayedIntField(intRect, value);
|
||||
|
||||
if (value > uiMax)
|
||||
uiMax = value;
|
||||
|
||||
if (value < uiMin)
|
||||
uiMin = value;
|
||||
|
||||
expanded = EditorGUI.Foldout(foldoutRect, expanded, GUIContent.none);
|
||||
if (expanded)
|
||||
{
|
||||
Rect rangeLabelRect = new Rect(sliderRect.x, sliderRect.yMax, sliderRect.width / 2, intRect.height);
|
||||
Rect minRect = new Rect(intRect.x - (intRect.width + indentOffset + PAD), sliderRect.yMax + 2f, intRect.width, intRect.height);
|
||||
Rect maxRect = new Rect(intRect.x, sliderRect.yMax + 2f, intRect.width, intRect.height);
|
||||
|
||||
EditorGUI.PrefixLabel(rangeLabelRect, new GUIContent("Range:"));
|
||||
uiMin = UnityEditor.EditorGUI.DelayedIntField(minRect, uiMin);
|
||||
uiMin = UnityEngine.ProBuilder.Math.Clamp(uiMin, min, uiMax);
|
||||
if (value < uiMin)
|
||||
value = uiMin;
|
||||
uiMax = UnityEditor.EditorGUI.DelayedIntField(maxRect, uiMax);
|
||||
uiMax = UnityEngine.ProBuilder.Math.Clamp(uiMax, uiMin + 1, max);
|
||||
if (value > uiMax)
|
||||
value = uiMax;
|
||||
}
|
||||
|
||||
return UnityEngine.ProBuilder.Math.Clamp(value, min, max);
|
||||
}
|
||||
|
||||
public static bool ToolSettingsGUI(string text,
|
||||
string description,
|
||||
bool showSettings,
|
||||
Func<ProBuilderMesh[], ActionResult> action,
|
||||
Action gui,
|
||||
ProBuilderMesh[] selection)
|
||||
{
|
||||
return ToolSettingsGUI(text, description, showSettings, action, gui, true, selection);
|
||||
}
|
||||
|
||||
public static bool ToolSettingsGUI(string text,
|
||||
string description,
|
||||
bool showSettings,
|
||||
Func<ProBuilderMesh[], ActionResult> action,
|
||||
Action gui,
|
||||
bool enabled,
|
||||
ProBuilderMesh[] selection)
|
||||
{
|
||||
if (enabled)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
if (GUILayout.Button(new GUIContent(text, description), UnityEditor.EditorStyles.miniButtonLeft))
|
||||
action(selection);
|
||||
|
||||
if (GUILayout.Button(showSettings ? "-" : "+", UnityEditor.EditorStyles.miniButtonRight, GUILayout.MaxWidth(24)))
|
||||
showSettings = !showSettings;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (showSettings)
|
||||
{
|
||||
GUILayout.BeginVertical(EditorStyles.sceneTextBox);
|
||||
gui();
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.Space(4);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button(new GUIContent(text, description), UnityEditor.EditorStyles.miniButton))
|
||||
action(selection);
|
||||
}
|
||||
|
||||
return showSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to EditorGUILayout.Slider, except that this won't return clamped values
|
||||
* unless the user modifies the value.
|
||||
*/
|
||||
public static float Slider(GUIContent content, float value, float min, float max)
|
||||
{
|
||||
float tmp = value;
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
tmp = UnityEditor.EditorGUILayout.Slider(content, value, min, max);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
return tmp;
|
||||
else
|
||||
return value;
|
||||
}
|
||||
|
||||
static Rect sceneLabelRect = new Rect(0f, 0f, 0f, 0f);
|
||||
static Color SceneLabelBackgroundColor = new Color(.12f, .12f, .12f, 1f);
|
||||
|
||||
static GUIStyle sceneBoldLabel
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_sceneBoldLabel == null)
|
||||
{
|
||||
_sceneBoldLabel = new GUIStyle(UnityEditor.EditorStyles.boldLabel);
|
||||
_sceneBoldLabel.normal.textColor = Color.white;
|
||||
}
|
||||
return _sceneBoldLabel;
|
||||
}
|
||||
}
|
||||
|
||||
static GUIStyle _sceneBoldLabel = null;
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
Styles.ResetForPlayMode();
|
||||
_splitStyle = null;
|
||||
_centeredGreyMiniLabel = null;
|
||||
_solidBackgroundStyle = null;
|
||||
_buttonNoBackgroundSmallMarginStyle = null;
|
||||
_sceneBoldLabel = null;
|
||||
sceneLabelRect = new Rect(0f, 0f, 0f, 0f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draw a label in the scene view with a solid color background.
|
||||
*/
|
||||
public static void SceneLabel(string text, Vector2 position, bool center = true)
|
||||
{
|
||||
GUIContent gc = TempContent(text);
|
||||
|
||||
float width = UnityEditor.EditorStyles.boldLabel.CalcSize(gc).x;
|
||||
float height = UnityEditor.EditorStyles.label.CalcHeight(gc, width) + 4;
|
||||
|
||||
sceneLabelRect.x = center ? position.x - width * .5f : position.x;
|
||||
sceneLabelRect.y = center ? position.y - height * .5f : position.y - height;
|
||||
sceneLabelRect.width = width;
|
||||
sceneLabelRect.height = height;
|
||||
|
||||
DrawSolidColor(sceneLabelRect, SceneLabelBackgroundColor);
|
||||
|
||||
GUI.Label(sceneLabelRect, gc, sceneBoldLabel);
|
||||
}
|
||||
|
||||
public static SelectMode DoElementModeToolbar(Rect rect, SelectMode mode)
|
||||
{
|
||||
Styles.Init();
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var textureMode = mode.ContainsFlag(SelectMode.TextureVertex | SelectMode.TextureEdge | SelectMode.TextureFace);
|
||||
|
||||
int currentSelectionMode = -1;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case SelectMode.Vertex:
|
||||
case SelectMode.TextureVertex:
|
||||
currentSelectionMode = 1;
|
||||
break;
|
||||
case SelectMode.Edge:
|
||||
case SelectMode.TextureEdge:
|
||||
currentSelectionMode = 2;
|
||||
break;
|
||||
case SelectMode.Face:
|
||||
case SelectMode.TextureFace:
|
||||
currentSelectionMode = 3;
|
||||
break;
|
||||
default:
|
||||
currentSelectionMode = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
currentSelectionMode = GUI.Toolbar(rect, currentSelectionMode, Styles.selectModeIcons, Styles.command);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
mode = currentSelectionMode switch
|
||||
{
|
||||
1 => textureMode ? SelectMode.TextureVertex : SelectMode.Vertex,
|
||||
2 => textureMode ? SelectMode.TextureEdge : SelectMode.Edge,
|
||||
3 => textureMode ? SelectMode.TextureFace : SelectMode.Face,
|
||||
_ => mode
|
||||
};
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a931ba2bdb160804eb3438f28aa0def3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+520
@@ -0,0 +1,520 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEditor.SettingsManagement;
|
||||
using UnityObject = UnityEngine.Object;
|
||||
using Edge = UnityEngine.ProBuilder.Edge;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
static partial class EditorHandleDrawing
|
||||
{
|
||||
const HideFlags k_ResourceHideFlags = HideFlags.HideAndDontSave;
|
||||
const float k_MinLineWidthForGeometryShader = .01f;
|
||||
static readonly Color k_OccludedTint = new Color(.75f, .75f, .75f, 1f);
|
||||
|
||||
static bool s_Initialized;
|
||||
|
||||
static ObjectPool<Mesh> s_MeshPool2;
|
||||
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> s_WireHandles;
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> s_VertexHandles;
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> s_SelectedFaceHandles;
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> s_SelectedVertexHandles;
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> s_SelectedEdgeHandles;
|
||||
|
||||
// Edge, vert, wire, and line materials Can be either point to a geometry shader or an alternative for devices
|
||||
// without geometry shader support
|
||||
static Material s_EdgeMaterial;
|
||||
static Material s_VertMaterial;
|
||||
static Material s_WireMaterial;
|
||||
static Material s_LineMaterial;
|
||||
static Material s_FaceMaterial;
|
||||
static Material s_GlWireMaterial;
|
||||
|
||||
static Material edgeMaterial { get { Init(); return s_EdgeMaterial; } }
|
||||
static Material vertMaterial { get { Init(); return s_VertMaterial; } }
|
||||
static Material wireMaterial { get { Init(); return s_WireMaterial; } }
|
||||
static Material lineMaterial { get { Init(); return s_LineMaterial; } }
|
||||
static Material faceMaterial { get { Init(); return s_FaceMaterial; } }
|
||||
static Material glWireMaterial { get { Init(); return s_GlWireMaterial; } }
|
||||
|
||||
static ObjectPool<Mesh> meshPool { get { Init(); return s_MeshPool2; } }
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> wireHandles { get { Init(); return s_WireHandles; } }
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> vertexHandles { get { Init(); return s_VertexHandles; } }
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> selectedFaceHandles { get { Init(); return s_SelectedFaceHandles; } }
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> selectedVertexHandles { get { Init(); return s_SelectedVertexHandles; } }
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> selectedEdgeHandles { get { Init(); return s_SelectedEdgeHandles; } }
|
||||
|
||||
static Color wireframeColor { get { return s_UseUnityColors ? k_WireframeDefault : s_WireframeColorPref; } }
|
||||
internal static Color faceSelectedColor { get { return s_UseUnityColors ? Handles.selectedColor : s_SelectedFaceColorPref; } }
|
||||
internal static Color preselectionColor { get { return s_UseUnityColors ? Handles.preselectionColor : s_PreselectionColorPref; } }
|
||||
internal static Color edgeSelectedColor { get { return s_UseUnityColors ? Handles.selectedColor : s_SelectedEdgeColorPref; } }
|
||||
static Color edgeUnselectedColor { get { return s_UseUnityColors ? k_WireframeDefault : s_UnselectedEdgeColorPref; } }
|
||||
internal static Color vertexSelectedColor { get { return s_UseUnityColors ? Handles.selectedColor : s_SelectedVertexColorPref; } }
|
||||
static Color vertexUnselectedColor { get { return s_UseUnityColors ? k_VertexUnselectedDefault : s_UnselectedVertexColorPref; } }
|
||||
|
||||
// Force line rendering to use GL.LINE without geometry shader billboards. This is set by the
|
||||
// EnsureResourcesLoaded function based on available graphics API
|
||||
static bool m_ForceEdgeLinesGL;
|
||||
static bool m_ForceWireframeLinesGL;
|
||||
|
||||
|
||||
static Dictionary<ProBuilderMesh, MeshHandle> s_TemporaryHandles;
|
||||
|
||||
static readonly Color k_VertexUnselectedDefault = new Color(.7f, .7f, .7f, 1f);
|
||||
static readonly Color k_WireframeDefault = new Color(94.0f / 255.0f, 119.0f / 255.0f, 155.0f / 255.0f, 1f);
|
||||
|
||||
[UserSetting] static Pref<bool> s_UseUnityColors =
|
||||
new Pref<bool>("graphics.handlesUseUnityColors", true, SettingsScope.User);
|
||||
|
||||
[UserSetting] static Pref<bool> s_DitherFaceHandle =
|
||||
new Pref<bool>("graphics.ditherFaceHandles", true, SettingsScope.User);
|
||||
|
||||
[UserSetting] static Pref<Color> s_SelectedFaceColorPref = new Pref<Color>("graphics.userSelectedFaceColor",
|
||||
new Color(0f, 210f / 255f, 239f / 255f, 1f), SettingsScope.User);
|
||||
|
||||
[UserSetting] static Pref<Color> s_WireframeColorPref = new Pref<Color>("graphics.userWireframeColor",
|
||||
new Color(125f / 255f, 155f / 255f, 185f / 255f, 1f), SettingsScope.User);
|
||||
|
||||
[UserSetting] static Pref<Color> s_UnselectedEdgeColorPref = new Pref<Color>("graphics.userUnselectedEdgeColor",
|
||||
new Color(44f / 255f, 44f / 255f, 44f / 255f, 1f), SettingsScope.User);
|
||||
|
||||
[UserSetting] static Pref<Color> s_SelectedEdgeColorPref = new Pref<Color>("graphics.userSelectedEdgeColor",
|
||||
new Color(0f, 210f / 255f, 239f / 255f, 1f), SettingsScope.User);
|
||||
|
||||
[UserSetting] static Pref<Color> s_UnselectedVertexColorPref = new Pref<Color>(
|
||||
"graphics.userUnselectedVertexColor", new Color(44f / 255f, 44f / 255f, 44f / 255f, 1f),
|
||||
SettingsScope.User);
|
||||
|
||||
[UserSetting] static Pref<Color> s_SelectedVertexColorPref = new Pref<Color>("graphics.userSelectedVertexColor",
|
||||
new Color(0f, 210f / 255f, 239f / 255f, 1f), SettingsScope.User);
|
||||
|
||||
[UserSetting] static Pref<Color> s_PreselectionColorPref = new Pref<Color>("graphics.userPreselectionColor",
|
||||
new Color(179f / 255f, 246f / 255f, 255f / 255f, 1f), SettingsScope.User);
|
||||
|
||||
[UserSetting] static Pref<float> s_WireframeLineSize =
|
||||
new Pref<float>("graphics.wireframeLineSize", .5f, SettingsScope.User);
|
||||
|
||||
[UserSetting]
|
||||
static Pref<float> s_EdgeLineSize = new Pref<float>("graphics.edgeLineSize", 1f, SettingsScope.User);
|
||||
|
||||
[UserSetting]
|
||||
static Pref<float> s_VertexPointSize = new Pref<float>("graphics.vertexPointSize", 3f, SettingsScope.User);
|
||||
|
||||
[UserSetting]
|
||||
static Pref<bool> s_XRayView = new Pref<bool>("graphics.xRayView", true, SettingsScope.User);
|
||||
|
||||
[Obsolete]
|
||||
static Pref<bool> s_DepthTestHandles = new Pref<bool>("graphics.handleZTest", true, SettingsScope.User);
|
||||
|
||||
static readonly GUIContent k_XRaySetting = new GUIContent("Selection X-Ray", "When enabled, selected mesh elements that are occluded by geometry will be rendered with a faded appearance.");
|
||||
|
||||
[UserSettingBlock("Graphics")]
|
||||
static void HandleColorPreferences(string searchContext)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
s_XRayView.value = SettingsGUILayout.SettingsToggle(k_XRaySetting, s_XRayView, searchContext);
|
||||
|
||||
s_UseUnityColors.value = SettingsGUILayout.SettingsToggle("Use Unity Colors", s_UseUnityColors, searchContext);
|
||||
|
||||
if(!s_UseUnityColors.value)
|
||||
{
|
||||
using(new SettingsGUILayout.IndentedGroup())
|
||||
{
|
||||
s_DitherFaceHandle.value =
|
||||
SettingsGUILayout.SettingsToggle("Dither Face Overlay", s_DitherFaceHandle, searchContext);
|
||||
s_WireframeColorPref.value =
|
||||
SettingsGUILayout.SettingsColorField("Wireframe", s_WireframeColorPref, searchContext);
|
||||
s_PreselectionColorPref.value =
|
||||
SettingsGUILayout.SettingsColorField("Preselection", s_PreselectionColorPref, searchContext);
|
||||
s_SelectedFaceColorPref.value = SettingsGUILayout.SettingsColorField("Selected Face Color",
|
||||
s_SelectedFaceColorPref, searchContext);
|
||||
s_UnselectedEdgeColorPref.value = SettingsGUILayout.SettingsColorField("Unselected Edge Color",
|
||||
s_UnselectedEdgeColorPref, searchContext);
|
||||
s_SelectedEdgeColorPref.value = SettingsGUILayout.SettingsColorField("Selected Edge Color",
|
||||
s_SelectedEdgeColorPref, searchContext);
|
||||
s_UnselectedVertexColorPref.value = SettingsGUILayout.SettingsColorField("Unselected Vertex Color",
|
||||
s_UnselectedVertexColorPref, searchContext);
|
||||
s_SelectedVertexColorPref.value = SettingsGUILayout.SettingsColorField("Selected Vertex Color",
|
||||
s_SelectedVertexColorPref, searchContext);
|
||||
}
|
||||
}
|
||||
|
||||
s_VertexPointSize.value = SettingsGUILayout.SettingsSlider("Vertex Size", s_VertexPointSize, 1f, 10f, searchContext);
|
||||
s_EdgeLineSize.value = SettingsGUILayout.SettingsSlider("Line Size", s_EdgeLineSize, 0f, 10f, searchContext);
|
||||
s_WireframeLineSize.value = SettingsGUILayout.SettingsSlider("Wireframe Size", s_WireframeLineSize, 0f, 10f, searchContext);
|
||||
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
ProBuilderEditor.UpdateMeshHandles();
|
||||
}
|
||||
|
||||
internal static float dotCapSize
|
||||
{
|
||||
get { return s_VertexPointSize * .0125f; }
|
||||
}
|
||||
|
||||
internal static bool xRay
|
||||
{
|
||||
get => s_XRayView;
|
||||
set => s_XRayView.value = value;
|
||||
}
|
||||
|
||||
static void Init()
|
||||
{
|
||||
if (s_Initialized)
|
||||
return;
|
||||
|
||||
s_Initialized = true;
|
||||
|
||||
ReleaseResources();
|
||||
|
||||
s_MeshPool2 = new ObjectPool<Mesh>(0, 8, CreateMesh, DestroyMesh);
|
||||
s_WireHandles = new Dictionary<ProBuilderMesh, MeshHandle>();
|
||||
s_VertexHandles = new Dictionary<ProBuilderMesh, MeshHandle>();
|
||||
s_SelectedFaceHandles = new Dictionary<ProBuilderMesh, MeshHandle>();
|
||||
s_SelectedEdgeHandles = new Dictionary<ProBuilderMesh, MeshHandle>();
|
||||
s_SelectedVertexHandles = new Dictionary<ProBuilderMesh, MeshHandle>();
|
||||
|
||||
s_TemporaryHandles = new Dictionary<ProBuilderMesh, MeshHandle>();
|
||||
|
||||
var lineShader = BuiltinMaterials.geometryShadersSupported
|
||||
? BuiltinMaterials.lineShader
|
||||
: BuiltinMaterials.lineShaderMetal;
|
||||
var vertShader = BuiltinMaterials.geometryShadersSupported
|
||||
? BuiltinMaterials.pointShader
|
||||
: BuiltinMaterials.dotShader;
|
||||
|
||||
s_EdgeMaterial = CreateMaterial(Shader.Find(lineShader), "ProBuilder::LineMaterial");
|
||||
s_WireMaterial = CreateMaterial(Shader.Find(lineShader), "ProBuilder::WireMaterial");
|
||||
s_LineMaterial = CreateMaterial(Shader.Find(lineShader), "ProBuilder::GeneralUseLineMaterial");
|
||||
s_VertMaterial = CreateMaterial(Shader.Find(vertShader), "ProBuilder::VertexMaterial");
|
||||
s_GlWireMaterial = CreateMaterial(Shader.Find(BuiltinMaterials.faceShader), "ProBuilder::GLWire");
|
||||
s_FaceMaterial = CreateMaterial(Shader.Find(BuiltinMaterials.faceShader), "ProBuilder::FaceMaterial");
|
||||
|
||||
ResetPreferences();
|
||||
}
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
ReleaseResources();
|
||||
s_Initialized = false;
|
||||
s_WireHandles = null;
|
||||
s_VertexHandles = null;
|
||||
s_SelectedFaceHandles = null;
|
||||
s_SelectedVertexHandles = null;
|
||||
s_SelectedEdgeHandles = null;
|
||||
s_TemporaryHandles = null;
|
||||
}
|
||||
|
||||
internal static void ReleaseResources()
|
||||
{
|
||||
ClearHandles();
|
||||
if (s_MeshPool2 != null) s_MeshPool2.Dispose();
|
||||
if (s_FaceMaterial != null) UnityObject.DestroyImmediate(s_FaceMaterial);
|
||||
if (s_EdgeMaterial != null) UnityObject.DestroyImmediate(s_EdgeMaterial);
|
||||
if (s_VertMaterial != null) UnityObject.DestroyImmediate(s_VertMaterial);
|
||||
if (s_WireMaterial != null) UnityObject.DestroyImmediate(s_WireMaterial);
|
||||
if (s_LineMaterial != null) UnityObject.DestroyImmediate(s_LineMaterial);
|
||||
if (s_GlWireMaterial != null) UnityObject.DestroyImmediate(s_GlWireMaterial);
|
||||
s_MeshPool2 = null;
|
||||
s_FaceMaterial = null;
|
||||
s_EdgeMaterial = null;
|
||||
s_VertMaterial = null;
|
||||
s_WireMaterial = null;
|
||||
s_LineMaterial = null;
|
||||
s_GlWireMaterial = null;
|
||||
}
|
||||
|
||||
internal static void ResetPreferences()
|
||||
{
|
||||
faceMaterial.SetFloat("_Dither", (s_UseUnityColors || s_DitherFaceHandle) ? 1f : 0f);
|
||||
|
||||
m_ForceEdgeLinesGL = s_EdgeLineSize.value < k_MinLineWidthForGeometryShader;
|
||||
m_ForceWireframeLinesGL = s_WireframeLineSize.value < k_MinLineWidthForGeometryShader;
|
||||
|
||||
wireMaterial.SetColor("_Color", wireframeColor);
|
||||
wireMaterial.SetFloat("_HandleZTest", (int)CompareFunction.LessEqual);
|
||||
|
||||
SetMaterialsScaleAttribute();
|
||||
}
|
||||
|
||||
static Material CreateMaterial(Shader shader, string materialName)
|
||||
{
|
||||
if(shader == null)
|
||||
shader = BuiltinMaterials.defaultMaterial.shader;
|
||||
|
||||
Material mat = new Material(shader);
|
||||
mat.name = materialName;
|
||||
mat.hideFlags = k_ResourceHideFlags;
|
||||
return mat;
|
||||
}
|
||||
|
||||
static Mesh CreateMesh()
|
||||
{
|
||||
var mesh = new Mesh();
|
||||
mesh.name = "EditorMeshHandles.MeshHandle" + mesh.GetObjectId();
|
||||
mesh.hideFlags = HideFlags.HideAndDontSave;
|
||||
return mesh;
|
||||
}
|
||||
|
||||
static void DestroyMesh(Mesh mesh)
|
||||
{
|
||||
if(mesh == null)
|
||||
throw new ArgumentNullException("mesh");
|
||||
|
||||
UnityObject.DestroyImmediate(mesh);
|
||||
}
|
||||
|
||||
public static void DrawSceneSelection(SceneSelection selection)
|
||||
{
|
||||
var mesh = selection.mesh;
|
||||
|
||||
if(mesh == null)
|
||||
return;
|
||||
|
||||
var positions = mesh.positionsInternal;
|
||||
|
||||
// Draw nearest edge
|
||||
using (new TriangleDrawingScope(preselectionColor))
|
||||
{
|
||||
GL.MultMatrix(mesh.transform.localToWorldMatrix);
|
||||
foreach(var face in selection.faces)
|
||||
{
|
||||
var ind = face.indexes;
|
||||
|
||||
for(int i = 0, c = ind.Count; i < c; i += 3)
|
||||
{
|
||||
GL.Vertex(positions[ind[i]]);
|
||||
GL.Vertex(positions[ind[i + 1]]);
|
||||
GL.Vertex(positions[ind[i + 2]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using (var drawingScope = new LineDrawingScope(preselectionColor, mesh.transform.localToWorldMatrix, -1f, CompareFunction.Always))
|
||||
{
|
||||
foreach(var edge in selection.edges)
|
||||
{
|
||||
drawingScope.DrawLine(positions[edge.a], positions[edge.b]);
|
||||
}
|
||||
}
|
||||
using (var drawingScope = new PointDrawingScope(preselectionColor, CompareFunction.Always) { matrix = mesh.transform.localToWorldMatrix })
|
||||
{
|
||||
foreach(var vertex in selection.vertexes)
|
||||
{
|
||||
drawingScope.Draw(positions[vertex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void DrawSceneHandles(SelectMode mode)
|
||||
{
|
||||
if (Event.current.type != EventType.Repaint)
|
||||
return;
|
||||
|
||||
// Update the scale based on EditorGUIUtility.pixelsPerPoints in case the DPI would have changed.
|
||||
SetMaterialsScaleAttribute();
|
||||
|
||||
switch(mode)
|
||||
{
|
||||
case SelectMode.Edge:
|
||||
case SelectMode.TextureEdge:
|
||||
{
|
||||
// When in Edge mode, use the same material for wireframe
|
||||
Render(wireHandles, m_ForceEdgeLinesGL ? glWireMaterial : edgeMaterial, edgeUnselectedColor, CompareFunction.LessEqual, false);
|
||||
if(xRay) Render(s_SelectedEdgeHandles, m_ForceEdgeLinesGL ? s_GlWireMaterial : s_EdgeMaterial, edgeSelectedColor * k_OccludedTint, CompareFunction.Greater);
|
||||
Render(s_SelectedEdgeHandles, m_ForceEdgeLinesGL ? s_GlWireMaterial : s_EdgeMaterial, edgeSelectedColor, CompareFunction.LessEqual);
|
||||
break;
|
||||
}
|
||||
case SelectMode.Face:
|
||||
case SelectMode.TextureFace:
|
||||
{
|
||||
Render(wireHandles, m_ForceWireframeLinesGL ? glWireMaterial : wireMaterial, wireframeColor, CompareFunction.LessEqual, false);
|
||||
if(xRay) Render(s_SelectedFaceHandles, s_FaceMaterial, faceSelectedColor * k_OccludedTint, CompareFunction.Greater);
|
||||
Render(s_SelectedFaceHandles, s_FaceMaterial, faceSelectedColor, CompareFunction.LessEqual);
|
||||
break;
|
||||
}
|
||||
case SelectMode.Vertex:
|
||||
case SelectMode.TextureVertex:
|
||||
{
|
||||
Render(s_WireHandles, m_ForceWireframeLinesGL ? s_GlWireMaterial : s_WireMaterial, wireframeColor, CompareFunction.LessEqual, false);
|
||||
Render(s_VertexHandles, s_VertMaterial, vertexUnselectedColor, CompareFunction.LessEqual, false);
|
||||
if(xRay) Render(s_SelectedVertexHandles, s_VertMaterial, vertexSelectedColor * k_OccludedTint, CompareFunction.Greater, false);
|
||||
Render(s_SelectedVertexHandles, s_VertMaterial, vertexSelectedColor, CompareFunction.LessEqual, false);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
Render(wireHandles, m_ForceWireframeLinesGL ? glWireMaterial : wireMaterial, wireframeColor, CompareFunction.LessEqual, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void Render(Dictionary<ProBuilderMesh, MeshHandle> handles, Material material, Color color, CompareFunction func, bool zWrite = false)
|
||||
{
|
||||
material.SetFloat("_HandleZTest", (int) func);
|
||||
material.SetFloat("_HandleZWrite", zWrite ? 1 : 0);
|
||||
material.SetColor("_Color", color);
|
||||
|
||||
if(material.SetPass(0))
|
||||
{
|
||||
foreach(var kvp in handles)
|
||||
kvp.Value.DrawMeshNow(0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void ClearHandles()
|
||||
{
|
||||
if(s_WireHandles != null)
|
||||
ClearHandlesInternal(wireHandles);
|
||||
if(s_VertexHandles != null)
|
||||
ClearHandlesInternal(s_VertexHandles);
|
||||
if(selectedFaceHandles != null)
|
||||
ClearHandlesInternal(s_SelectedFaceHandles);
|
||||
if(s_SelectedEdgeHandles != null)
|
||||
ClearHandlesInternal(s_SelectedEdgeHandles);
|
||||
if(s_SelectedVertexHandles != null)
|
||||
ClearHandlesInternal(s_SelectedVertexHandles);
|
||||
if(s_TemporaryHandles != null)
|
||||
ClearHandlesInternal(s_TemporaryHandles);
|
||||
}
|
||||
|
||||
public static void RebuildSelectedHandles(IEnumerable<ProBuilderMesh> meshes, SelectMode selectionMode)
|
||||
{
|
||||
ClearHandles();
|
||||
|
||||
foreach(var mesh in meshes)
|
||||
{
|
||||
switch(selectionMode)
|
||||
{
|
||||
case SelectMode.Vertex:
|
||||
case SelectMode.TextureVertex:
|
||||
{
|
||||
RebuildMeshHandle(mesh, vertexHandles, MeshHandles.CreateVertexMesh);
|
||||
var handle = GetMeshHandle(mesh, selectedVertexHandles);
|
||||
MeshHandles.CreateVertexMesh(mesh, handle.mesh, mesh.selectedIndexesInternal);
|
||||
goto default;
|
||||
}
|
||||
|
||||
case SelectMode.Edge:
|
||||
case SelectMode.TextureEdge:
|
||||
{
|
||||
if(m_ForceEdgeLinesGL || BuiltinMaterials.geometryShadersSupported)
|
||||
RebuildMeshHandle(mesh, wireHandles, MeshHandles.CreateEdgeMesh);
|
||||
else
|
||||
RebuildMeshHandle(mesh, wireHandles, MeshHandles.CreateEdgeBillboardMesh);
|
||||
|
||||
var handle = GetMeshHandle(mesh, selectedEdgeHandles);
|
||||
|
||||
if(m_ForceEdgeLinesGL || BuiltinMaterials.geometryShadersSupported)
|
||||
MeshHandles.CreateEdgeMesh(mesh, handle.mesh, mesh.selectedEdgesInternal);
|
||||
else
|
||||
MeshHandles.CreateEdgeBillboardMesh(mesh, handle.mesh, mesh.selectedEdgesInternal);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SelectMode.Face:
|
||||
case SelectMode.TextureFace:
|
||||
{
|
||||
RebuildMeshHandle(mesh, selectedFaceHandles, MeshHandles.CreateFaceMesh);
|
||||
goto default;
|
||||
}
|
||||
|
||||
default:
|
||||
if(m_ForceWireframeLinesGL || BuiltinMaterials.geometryShadersSupported)
|
||||
RebuildMeshHandle(mesh, wireHandles, MeshHandles.CreateEdgeMesh);
|
||||
else
|
||||
RebuildMeshHandle(mesh, wireHandles, MeshHandles.CreateEdgeBillboardMesh);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static MeshHandle GetMeshHandle(ProBuilderMesh mesh, Dictionary<ProBuilderMesh, MeshHandle> cache)
|
||||
{
|
||||
MeshHandle handle;
|
||||
|
||||
if(!cache.TryGetValue(mesh, out handle))
|
||||
{
|
||||
var m = meshPool.Dequeue();
|
||||
handle = new MeshHandle(mesh.transform, m);
|
||||
cache.Add(mesh, handle);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
static void RebuildMeshHandle(ProBuilderMesh mesh, Dictionary<ProBuilderMesh, MeshHandle> list, Action<ProBuilderMesh, Mesh> ctor)
|
||||
{
|
||||
var handle = GetMeshHandle(mesh, list);
|
||||
ctor(mesh, handle.mesh);
|
||||
}
|
||||
|
||||
static void RebuildMeshHandleFromFaces(ProBuilderMesh mesh, IList<Face> faces,
|
||||
Dictionary<ProBuilderMesh, MeshHandle> list, Action<ProBuilderMesh, IList<Face>, Mesh> ctor)
|
||||
{
|
||||
var handle = GetMeshHandle(mesh, list);
|
||||
ctor(mesh, faces, handle.mesh);
|
||||
}
|
||||
|
||||
static void ClearHandlesInternal(Dictionary<ProBuilderMesh, MeshHandle> handles)
|
||||
{
|
||||
foreach (var kvp in handles)
|
||||
meshPool.Enqueue(kvp.Value.mesh);
|
||||
handles.Clear();
|
||||
}
|
||||
|
||||
static void SetMaterialsScaleAttribute()
|
||||
{
|
||||
vertMaterial.SetFloat("_Scale", s_VertexPointSize * EditorGUIUtility.pixelsPerPoint);
|
||||
wireMaterial.SetFloat("_Scale", s_WireframeLineSize * EditorGUIUtility.pixelsPerPoint);
|
||||
edgeMaterial.SetFloat("_Scale", s_EdgeLineSize * EditorGUIUtility.pixelsPerPoint);
|
||||
}
|
||||
|
||||
public static void HighlightFaces(ProBuilderMesh mesh, IList<Face> faces, Color highlightColor)
|
||||
{
|
||||
RebuildMeshHandleFromFaces(mesh, faces, s_TemporaryHandles, MeshHandles.CreateFaceMeshFromFaces);
|
||||
Render(s_TemporaryHandles, s_FaceMaterial, highlightColor,CompareFunction.LessEqual, false);
|
||||
}
|
||||
|
||||
public static void HighlightEdges(ProBuilderMesh mesh, IList<Edge> edges, bool highlight = true)
|
||||
{
|
||||
HighlightEdges(mesh,edges, highlight ? edgeSelectedColor : edgeUnselectedColor);
|
||||
}
|
||||
|
||||
public static void HighlightEdges(ProBuilderMesh mesh, IList<Edge> edges, Color highlightColor)
|
||||
{
|
||||
var handle = GetMeshHandle(mesh, s_TemporaryHandles);
|
||||
|
||||
if(m_ForceEdgeLinesGL || BuiltinMaterials.geometryShadersSupported)
|
||||
MeshHandles.CreateEdgeMesh(mesh, handle.mesh, edges.ToArray());
|
||||
else
|
||||
MeshHandles.CreateEdgeBillboardMesh(mesh, handle.mesh, edges.ToArray());
|
||||
|
||||
Render(s_TemporaryHandles, s_EdgeMaterial, highlightColor, CompareFunction.LessEqual, false);
|
||||
}
|
||||
|
||||
public static void HighlightVertices(ProBuilderMesh mesh, IList<int> vertexIndexes, bool highlight = true)
|
||||
{
|
||||
HighlightVertices(mesh,vertexIndexes, highlight ? vertexSelectedColor : vertexUnselectedColor);
|
||||
}
|
||||
|
||||
public static void HighlightVertices(ProBuilderMesh mesh, IList<int> vertexIndexes, Color highlightColor)
|
||||
{
|
||||
var handle = GetMeshHandle(mesh, s_TemporaryHandles);
|
||||
MeshHandles.CreateVertexMesh(mesh, handle.mesh, vertexIndexes);
|
||||
|
||||
Render(s_TemporaryHandles, s_VertMaterial, highlightColor, CompareFunction.LessEqual, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5abceb5fc06c0954682d5b5addb0c3d4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+372
@@ -0,0 +1,372 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UObject = UnityEngine.Object;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
static partial class EditorHandleDrawing
|
||||
{
|
||||
const float k_DefaultGizmoSize = .2f;
|
||||
|
||||
internal static void DrawGizmo(Vector3 position, Quaternion rotation, float size = -1f)
|
||||
{
|
||||
var p = position;
|
||||
size = HandleUtility.GetHandleSize(p) * size < 0f ? .5f : size;
|
||||
|
||||
using (var lineDrawer = new LineDrawingScope(Color.green, -1f, CompareFunction.Always))
|
||||
{
|
||||
lineDrawer.DrawLine(p, p + rotation * Vector3.up * size);
|
||||
lineDrawer.SetColor(Color.red);
|
||||
lineDrawer.DrawLine(p, p + rotation * Vector3.right * size);
|
||||
lineDrawer.SetColor(Color.blue);
|
||||
lineDrawer.DrawLine(p, p + rotation * Vector3.forward * size);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DrawGizmo(Vector3 position, Matrix4x4 matrix, float size = -1f)
|
||||
{
|
||||
var p = matrix.MultiplyPoint3x4(position);
|
||||
size = HandleUtility.GetHandleSize(p) * size < 0f ? k_DefaultGizmoSize : size;
|
||||
|
||||
using (var lineDrawer = new LineDrawingScope(Color.green, -1f, CompareFunction.Always))
|
||||
{
|
||||
lineDrawer.DrawLine(p, p + matrix.MultiplyVector(Vector3.up) * size);
|
||||
lineDrawer.SetColor(Color.red);
|
||||
lineDrawer.DrawLine(p, p + matrix.MultiplyVector(Vector3.right) * size);
|
||||
lineDrawer.SetColor(Color.blue);
|
||||
lineDrawer.DrawLine(p, p + matrix.MultiplyVector(Vector3.forward) * size);
|
||||
}
|
||||
}
|
||||
|
||||
internal static void DrawTransformOriginGizmo(Matrix4x4 matrix, Vector3 direction, float size = -1f)
|
||||
{
|
||||
var p = matrix.MultiplyPoint(Vector3.zero);
|
||||
var s = HandleUtility.GetHandleSize(p);
|
||||
var d = size < 0f ? k_DefaultGizmoSize : size;
|
||||
var e = Event.current.type;
|
||||
|
||||
Handles.color = Color.gray;
|
||||
Handles.DotHandleCap(0, p, Quaternion.identity, s * dotCapSize, e);
|
||||
Handles.DotHandleCap(0, p + matrix.MultiplyVector(direction) * d, Quaternion.identity, s * dotCapSize, e);
|
||||
Handles.DrawLine(p, p + matrix.MultiplyVector(direction) * d);
|
||||
Handles.color = Color.white;
|
||||
}
|
||||
|
||||
public struct PointDrawingScope : IDisposable
|
||||
{
|
||||
Color m_Color;
|
||||
CompareFunction m_ZTest;
|
||||
bool m_IsDisposed;
|
||||
Mesh m_Mesh;
|
||||
List<Vector3> m_Points;
|
||||
List<int> m_Indices;
|
||||
Matrix4x4 m_Matrix;
|
||||
|
||||
public Color color
|
||||
{
|
||||
get { return m_Color; }
|
||||
set
|
||||
{
|
||||
End();
|
||||
m_Color = value;
|
||||
Begin();
|
||||
}
|
||||
}
|
||||
|
||||
public CompareFunction zTest
|
||||
{
|
||||
get { return m_ZTest; }
|
||||
|
||||
set
|
||||
{
|
||||
End();
|
||||
m_ZTest = value;
|
||||
Begin();
|
||||
}
|
||||
}
|
||||
|
||||
public Matrix4x4 matrix
|
||||
{
|
||||
get { return m_Matrix; }
|
||||
set { m_Matrix = value; }
|
||||
}
|
||||
|
||||
public PointDrawingScope(Color color, CompareFunction zTest = CompareFunction.LessEqual)
|
||||
{
|
||||
m_Color = color;
|
||||
m_ZTest = zTest;
|
||||
m_IsDisposed = false;
|
||||
m_Mesh = meshPool.Dequeue();
|
||||
m_Points = new List<Vector3>(64);
|
||||
m_Indices = new List<int>(64);
|
||||
m_Matrix = Matrix4x4.identity;
|
||||
Begin();
|
||||
}
|
||||
|
||||
void Begin()
|
||||
{
|
||||
vertMaterial.SetColor("_Color", color);
|
||||
vertMaterial.SetFloat("_HandleZTest", (int)zTest);
|
||||
|
||||
if (!vertMaterial.SetPass(0))
|
||||
throw new Exception("Failed initializing vertex material.");
|
||||
|
||||
m_Points.Clear();
|
||||
}
|
||||
|
||||
void End()
|
||||
{
|
||||
m_Mesh.Clear();
|
||||
|
||||
if (BuiltinMaterials.geometryShadersSupported)
|
||||
{
|
||||
for (int i = 0, c = m_Points.Count; i < c; ++i)
|
||||
m_Indices.Add(i);
|
||||
|
||||
m_Mesh.SetVertices(m_Points);
|
||||
|
||||
m_Mesh.SetIndices(m_Indices, MeshTopology.Points, 0, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
MeshHandles.CreatePointBillboardMesh(m_Points, m_Mesh);
|
||||
}
|
||||
|
||||
Graphics.DrawMeshNow(m_Mesh, m_Matrix, 0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_IsDisposed)
|
||||
return;
|
||||
|
||||
m_IsDisposed = true;
|
||||
|
||||
End();
|
||||
|
||||
if(m_Mesh != null)
|
||||
meshPool.Enqueue(m_Mesh);
|
||||
}
|
||||
|
||||
public void Draw(Vector3 point)
|
||||
{
|
||||
m_Points.Add(point);
|
||||
}
|
||||
}
|
||||
|
||||
public struct LineDrawingScope : IDisposable
|
||||
{
|
||||
bool m_Wire;
|
||||
bool m_LineTopology;
|
||||
Color m_Color;
|
||||
float m_Thickness;
|
||||
CompareFunction m_ZTest;
|
||||
Matrix4x4 m_Matrix;
|
||||
bool m_IsDisposed;
|
||||
|
||||
Mesh m_LineMesh;
|
||||
List<Vector3> m_Positions;
|
||||
List<Vector4> m_Tangents;
|
||||
List<Color> m_Colors;
|
||||
List<int> m_Indices;
|
||||
|
||||
public Color color
|
||||
{
|
||||
get { return m_Color; }
|
||||
}
|
||||
|
||||
public void SetColor(Color color)
|
||||
{
|
||||
if (!m_Wire)
|
||||
End();
|
||||
m_Color = color;
|
||||
if (!m_Wire)
|
||||
Begin();
|
||||
}
|
||||
|
||||
public float thickness
|
||||
{
|
||||
get { return m_Thickness; }
|
||||
}
|
||||
|
||||
public CompareFunction zTest
|
||||
{
|
||||
get { return m_ZTest; }
|
||||
}
|
||||
|
||||
public LineDrawingScope(Color color, float thickness = -1f, CompareFunction zTest = CompareFunction.LessEqual)
|
||||
: this(color, Matrix4x4.identity, thickness, zTest) { }
|
||||
|
||||
public LineDrawingScope(Color color, Matrix4x4 matrix, float thickness = -1f, CompareFunction zTest = CompareFunction.LessEqual)
|
||||
{
|
||||
m_LineMesh = meshPool.Dequeue();
|
||||
m_IsDisposed = false;
|
||||
m_Matrix = matrix;
|
||||
m_Color = color;
|
||||
m_Thickness = thickness < 0f ? s_EdgeLineSize : thickness;
|
||||
m_ZTest = zTest;
|
||||
|
||||
m_Positions = new List<Vector3>(4);
|
||||
m_Tangents = new List<Vector4>(4);
|
||||
m_Colors = new List<Color>(4);
|
||||
m_Indices = new List<int>(4);
|
||||
|
||||
m_Wire = m_Thickness < k_MinLineWidthForGeometryShader || lineMaterial == null;
|
||||
m_LineTopology = m_Wire || BuiltinMaterials.geometryShadersSupported;
|
||||
|
||||
Begin();
|
||||
}
|
||||
|
||||
void Begin()
|
||||
{
|
||||
if (!m_Wire)
|
||||
{
|
||||
lineMaterial.SetColor("_Color", color);
|
||||
lineMaterial.SetFloat("_Scale", thickness * EditorGUIUtility.pixelsPerPoint);
|
||||
lineMaterial.SetFloat("_HandleZTest", (int)zTest);
|
||||
}
|
||||
|
||||
if (m_Wire || !lineMaterial.SetPass(0))
|
||||
HandleUtility.ApplyWireMaterial(zTest);
|
||||
}
|
||||
|
||||
void End()
|
||||
{
|
||||
m_LineMesh.Clear();
|
||||
m_LineMesh.SetVertices(m_Positions);
|
||||
if(m_Wire)
|
||||
m_LineMesh.SetColors(m_Colors);
|
||||
else
|
||||
m_LineMesh.SetTangents(m_Tangents);
|
||||
m_LineMesh.subMeshCount = 1;
|
||||
m_LineMesh.SetIndices(m_Indices, m_LineTopology ? MeshTopology.Lines : MeshTopology.Quads, 0);
|
||||
|
||||
Graphics.DrawMeshNow(m_LineMesh, m_Matrix);
|
||||
|
||||
m_Positions.Clear();
|
||||
m_Tangents.Clear();
|
||||
m_Colors.Clear();
|
||||
m_Indices.Clear();
|
||||
}
|
||||
|
||||
public void DrawLine(Vector3 a, Vector3 b)
|
||||
{
|
||||
var count = m_Positions.Count;
|
||||
|
||||
if (!m_Wire && !m_LineTopology)
|
||||
{
|
||||
Vector3 c = b + (b - a);
|
||||
|
||||
m_Tangents.Add(new Vector4(b.x, b.y, b.z, 1f));
|
||||
m_Positions.Add(a);
|
||||
m_Indices.Add(count + 0);
|
||||
|
||||
m_Tangents.Add(new Vector4(b.x, b.y, b.z, -1f));
|
||||
m_Positions.Add(a);
|
||||
m_Indices.Add(count + 1);
|
||||
|
||||
m_Tangents.Add(new Vector4(c.x, c.y, c.z, -1f));
|
||||
m_Positions.Add(b);
|
||||
m_Indices.Add(count + 2);
|
||||
|
||||
m_Tangents.Add(new Vector4(c.x, c.y, c.z, 1f));
|
||||
m_Positions.Add(b);
|
||||
m_Indices.Add(count + 3);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Colors.Add(color);
|
||||
m_Positions.Add(a);
|
||||
m_Indices.Add(count + 0);
|
||||
|
||||
m_Colors.Add(color);
|
||||
m_Positions.Add(b);
|
||||
m_Indices.Add(count + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_IsDisposed)
|
||||
return;
|
||||
m_IsDisposed = true;
|
||||
End();
|
||||
if(m_LineMesh != null)
|
||||
meshPool.Enqueue(m_LineMesh);
|
||||
}
|
||||
}
|
||||
|
||||
internal class TriangleDrawingScope : IDisposable
|
||||
{
|
||||
Color m_Color;
|
||||
CompareFunction m_ZTest;
|
||||
bool m_IsDisposed;
|
||||
|
||||
public Color color
|
||||
{
|
||||
get { return m_Color; }
|
||||
set
|
||||
{
|
||||
End();
|
||||
m_Color = value;
|
||||
Begin();
|
||||
}
|
||||
}
|
||||
|
||||
public CompareFunction zTest
|
||||
{
|
||||
get { return m_ZTest; }
|
||||
|
||||
set
|
||||
{
|
||||
End();
|
||||
m_ZTest = value;
|
||||
Begin();
|
||||
}
|
||||
}
|
||||
|
||||
public TriangleDrawingScope(Color color, CompareFunction zTest = CompareFunction.LessEqual)
|
||||
{
|
||||
m_Color = color;
|
||||
m_ZTest = zTest;
|
||||
Begin();
|
||||
}
|
||||
|
||||
void Begin()
|
||||
{
|
||||
faceMaterial.SetColor("_Color", color);
|
||||
faceMaterial.SetFloat("_HandleZTest", (int)zTest);
|
||||
|
||||
if (!faceMaterial.SetPass(0))
|
||||
throw new Exception("Failed initializing face material.");
|
||||
|
||||
GL.PushMatrix();
|
||||
GL.Begin(GL.TRIANGLES);
|
||||
}
|
||||
|
||||
void End()
|
||||
{
|
||||
GL.End();
|
||||
GL.PopMatrix();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_IsDisposed)
|
||||
return;
|
||||
m_IsDisposed = true;
|
||||
End();
|
||||
}
|
||||
|
||||
public void Draw(Vector3 a, Vector3 b, Vector3 c)
|
||||
{
|
||||
GL.Vertex(a);
|
||||
GL.Vertex(b);
|
||||
GL.Vertex(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50a4f82d9b25145fb91d03a7bef21d24
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+737
@@ -0,0 +1,737 @@
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine.ProBuilder;
|
||||
using RaycastHit = UnityEngine.ProBuilder.RaycastHit;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Utilities for creating and manipulating Handles and points in GUI space.
|
||||
/// </summary>
|
||||
static class EditorHandleUtility
|
||||
{
|
||||
static Stack<Matrix4x4> s_HandleMatrix = new Stack<Matrix4x4>();
|
||||
|
||||
public static bool SceneViewInUse(Event e)
|
||||
{
|
||||
return Tools.viewToolActive || Tools.current == Tool.View || (e.modifiers & EventModifiers.Alt) == EventModifiers.Alt;
|
||||
}
|
||||
|
||||
public static bool IsAppendModifier(EventModifiers em)
|
||||
{
|
||||
return (em & EventModifiers.Shift) == EventModifiers.Shift ||
|
||||
(em & EventModifiers.Control) == EventModifiers.Control ||
|
||||
(em & EventModifiers.Alt) == EventModifiers.Alt ||
|
||||
(em & EventModifiers.Command) == EventModifiers.Command;
|
||||
}
|
||||
|
||||
public static bool IsSelectionAddModifier(EventModifiers em)
|
||||
{
|
||||
return (em & EventModifiers.Shift) == EventModifiers.Shift;
|
||||
}
|
||||
|
||||
public static bool IsSelectionAppendOrRemoveIfPresentModifier(EventModifiers em)
|
||||
{
|
||||
return (((Application.platform == RuntimePlatform.OSXEditor) && (em & EventModifiers.Command) == EventModifiers.Command) ||
|
||||
((Application.platform != RuntimePlatform.OSXEditor) && (em & EventModifiers.Control) == EventModifiers.Control));
|
||||
}
|
||||
|
||||
const int HANDLE_PADDING = 8;
|
||||
const int LEFT_MOUSE_BUTTON = 0;
|
||||
const int MIDDLE_MOUSE_BUTTON = 2;
|
||||
|
||||
static readonly Quaternion QuaternionUp = Quaternion.Euler(Vector3.right * 90f);
|
||||
static readonly Quaternion QuaternionRight = Quaternion.Euler(Vector3.up * 90f);
|
||||
static readonly Vector3 ConeDepth = new Vector3(0f, 0f, 16f);
|
||||
|
||||
static readonly Color k_HandleColorUp = new Color(0f, .7f, 0f, .8f);
|
||||
static readonly Color k_HandleColorRight = new Color(0f, 0f, .7f, .8f);
|
||||
static readonly Color k_HandleColorRotate = new Color(0f, .7f, 0f, .8f);
|
||||
static readonly Color k_HandleColorScale = new Color(.7f, .7f, .7f, .8f);
|
||||
|
||||
static Material s_HandleMaterial = null;
|
||||
|
||||
public static Material handleMaterial
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_HandleMaterial == null)
|
||||
s_HandleMaterial = (Material)EditorGUIUtility.LoadRequired("SceneView/2DHandleLines.mat");
|
||||
|
||||
return s_HandleMaterial;
|
||||
}
|
||||
}
|
||||
|
||||
public static int CurrentID { get { return currentId; } }
|
||||
static int currentId = -1;
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
if (s_HandleMaterial != null)
|
||||
Object.DestroyImmediate(s_HandleMaterial);
|
||||
s_HandleMaterial = null;
|
||||
currentId = -1;
|
||||
s_HandleMatrix.Clear();
|
||||
handleOffset = Vector2.zero;
|
||||
initialMousePosition = Vector2.zero;
|
||||
axisConstraint = new HandleConstraint2D(0, 0);
|
||||
}
|
||||
|
||||
static Vector2 handleOffset = Vector2.zero;
|
||||
static Vector2 initialMousePosition = Vector2.zero;
|
||||
|
||||
static HandleConstraint2D axisConstraint = new HandleConstraint2D(0, 0); // Multiply this value by input to mask axis movement.
|
||||
public static HandleConstraint2D CurrentAxisConstraint { get { return axisConstraint; } }
|
||||
|
||||
public static bool limitToLeftButton = true;
|
||||
|
||||
/**
|
||||
* A 2D GUI view position handle.
|
||||
* @param id The Handle id.
|
||||
* @param position The position in GUI coordinates.
|
||||
* @param size How large in pixels to draw this handle.
|
||||
*/
|
||||
public static Vector2 PositionHandle2d(int id, Vector2 position, int size)
|
||||
{
|
||||
int width = size / 4;
|
||||
var evt = Event.current;
|
||||
|
||||
Rect handleRectUp = new Rect(position.x - width / 2, position.y - size - HANDLE_PADDING, width, size + HANDLE_PADDING);
|
||||
Rect handleRectRight = new Rect(position.x, position.y - width / 2, size, width + HANDLE_PADDING);
|
||||
|
||||
if (evt.type == EventType.Repaint)
|
||||
{
|
||||
Handles.color = Color.yellow;
|
||||
Handles.CircleHandleCap(id, position, Quaternion.identity, width / 2f, evt.type);
|
||||
Handles.color = k_HandleColorUp;
|
||||
|
||||
// Y Line
|
||||
Handles.DrawLine(position, position - Vector2.up * size);
|
||||
|
||||
// Y Cone
|
||||
if (position.y - size > 0f)
|
||||
Handles.ConeHandleCap(id, ((Vector3) ((position - Vector2.up * size))) - ConeDepth, QuaternionUp,
|
||||
width / 2, evt.type);
|
||||
|
||||
Handles.color = k_HandleColorRight;
|
||||
|
||||
// X Line
|
||||
Handles.DrawLine(position, position + Vector2.right * size);
|
||||
|
||||
// X Cap
|
||||
if (position.y > 0f)
|
||||
Handles.ConeHandleCap(id, ((Vector3) ((position + Vector2.right * size))) - ConeDepth,
|
||||
QuaternionRight, width / 2, evt.type);
|
||||
}
|
||||
|
||||
// If a Tool already is engaged and it's not this one, bail.
|
||||
if (currentId >= 0 && currentId != id)
|
||||
return position;
|
||||
|
||||
Vector2 mousePosition = evt.mousePosition;
|
||||
Vector2 newPosition = position;
|
||||
|
||||
if (currentId == id)
|
||||
{
|
||||
switch (evt.type)
|
||||
{
|
||||
case EventType.MouseDrag:
|
||||
newPosition = axisConstraint.Mask(mousePosition + handleOffset) + axisConstraint.InverseMask(position);
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
case EventType.Ignore:
|
||||
currentId = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (evt.type == EventType.MouseDown && ((!limitToLeftButton && evt.button != MIDDLE_MOUSE_BUTTON) || evt.button == LEFT_MOUSE_BUTTON))
|
||||
{
|
||||
if (Vector2.Distance(mousePosition, position) < width / 2f)
|
||||
{
|
||||
currentId = id;
|
||||
handleOffset = position - mousePosition;
|
||||
axisConstraint = new HandleConstraint2D(1, 1);
|
||||
}
|
||||
else if (handleRectRight.Contains(mousePosition))
|
||||
{
|
||||
currentId = id;
|
||||
handleOffset = position - mousePosition;
|
||||
axisConstraint = new HandleConstraint2D(1, 0);
|
||||
}
|
||||
else if (handleRectUp.Contains(mousePosition))
|
||||
{
|
||||
currentId = id;
|
||||
handleOffset = position - mousePosition;
|
||||
axisConstraint = new HandleConstraint2D(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
static Vector2 s_InitialDirection;
|
||||
|
||||
/// <summary>
|
||||
/// A 2D rotation handle. Behaves like HandleUtility.RotationHandle
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="rotation"></param>
|
||||
/// <param name="radius"></param>
|
||||
/// <returns></returns>
|
||||
public static float RotationHandle2d(int id, Vector2 position, float rotation, int radius)
|
||||
{
|
||||
Event evt = Event.current;
|
||||
Vector2 mousePosition = evt.mousePosition;
|
||||
float newRotation = rotation;
|
||||
|
||||
Vector2 currentDirection = (mousePosition - position).normalized;
|
||||
|
||||
// Draw gizmos
|
||||
Handles.color = k_HandleColorRotate;
|
||||
Handles.CircleHandleCap(-1, position, Quaternion.identity, radius, evt.type);
|
||||
|
||||
if (currentId == id)
|
||||
{
|
||||
Handles.color = Color.gray;
|
||||
Handles.DrawLine(position, position + (mousePosition - position).normalized * radius);
|
||||
GUI.Label(new Rect(position.x, position.y, 90f, 30f), newRotation.ToString("F2") + PreferenceKeys.DEGREE_SYMBOL);
|
||||
}
|
||||
|
||||
// If a Tool already is engaged and it's not this one, bail.
|
||||
if (currentId >= 0 && currentId != id)
|
||||
return rotation;
|
||||
|
||||
if (currentId == id)
|
||||
{
|
||||
switch (evt.type)
|
||||
{
|
||||
case EventType.MouseDrag:
|
||||
|
||||
newRotation = Vector2.Angle(s_InitialDirection, currentDirection);
|
||||
|
||||
if (Vector2.Dot(new Vector2(-s_InitialDirection.y, s_InitialDirection.x), currentDirection) < 0)
|
||||
newRotation = 360f - newRotation;
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
case EventType.Ignore:
|
||||
currentId = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (evt.type == EventType.MouseDown && ((!limitToLeftButton && evt.button != MIDDLE_MOUSE_BUTTON) || evt.button == LEFT_MOUSE_BUTTON))
|
||||
{
|
||||
if (Mathf.Abs(Vector2.Distance(mousePosition, position) - radius) < 8)
|
||||
{
|
||||
currentId = id;
|
||||
initialMousePosition = mousePosition;
|
||||
s_InitialDirection = (initialMousePosition - position).normalized;
|
||||
handleOffset = position - mousePosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newRotation;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scale handle in 2d space.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="position"></param>
|
||||
/// <param name="scale"></param>
|
||||
/// <param name="size"></param>
|
||||
/// <returns></returns>
|
||||
public static Vector2 ScaleHandle2d(int id, Vector2 position, Vector2 scale, int size)
|
||||
{
|
||||
Event evt = Event.current;
|
||||
Vector2 mousePosition = evt.mousePosition;
|
||||
int width = size / 4;
|
||||
|
||||
if (evt.type == EventType.Repaint)
|
||||
{
|
||||
Handles.color = k_HandleColorUp;
|
||||
Handles.DrawLine(position, position - scale.y * size * Vector2.up);
|
||||
|
||||
if (position.y - size > 0f)
|
||||
Handles.CubeHandleCap(0,
|
||||
((Vector3) ((position - scale.y * size * Vector2.up))) - Vector3.forward * 16, QuaternionUp,
|
||||
width / 3, evt.type);
|
||||
|
||||
Handles.color = k_HandleColorRight;
|
||||
Handles.DrawLine(position, position + scale.x * size * Vector2.right);
|
||||
|
||||
if (position.y > 0f)
|
||||
Handles.CubeHandleCap(0,
|
||||
((Vector3) ((position + scale.x * size * Vector2.right))) - Vector3.forward * 16,
|
||||
Quaternion.Euler(Vector3.up * 90f),
|
||||
width / 3f,
|
||||
evt.type);
|
||||
|
||||
Handles.color = k_HandleColorScale;
|
||||
|
||||
Handles.CubeHandleCap(0,
|
||||
((Vector3) position) - Vector3.forward * 16,
|
||||
QuaternionUp,
|
||||
width / 2f,
|
||||
evt.type);
|
||||
}
|
||||
|
||||
// If a Tool already is engaged and it's not this one, bail.
|
||||
if (currentId >= 0 && currentId != id)
|
||||
return scale;
|
||||
|
||||
Rect handleRectUp = new Rect(position.x - width / 2f, position.y - size - HANDLE_PADDING, width, size + HANDLE_PADDING);
|
||||
Rect handleRectRight = new Rect(position.x, position.y - width / 2f, size + 8, width);
|
||||
Rect handleRectCenter = new Rect(position.x - width / 2f, position.y - width / 2f, width, width);
|
||||
|
||||
if (currentId == id)
|
||||
{
|
||||
switch (evt.type)
|
||||
{
|
||||
case EventType.MouseDrag:
|
||||
Vector2 diff = axisConstraint.Mask(mousePosition - initialMousePosition);
|
||||
diff.x += size;
|
||||
diff.y = -diff.y; // gui space Y is opposite-world
|
||||
diff.y += size;
|
||||
scale = diff / size;
|
||||
if (axisConstraint == HandleConstraint2D.None)
|
||||
{
|
||||
scale.x = Mathf.Min(scale.x, scale.y);
|
||||
scale.y = Mathf.Min(scale.x, scale.y);
|
||||
}
|
||||
break;
|
||||
|
||||
case EventType.MouseUp:
|
||||
case EventType.Ignore:
|
||||
currentId = -1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (evt.type == EventType.MouseDown && ((!limitToLeftButton && evt.button != MIDDLE_MOUSE_BUTTON) || evt.button == LEFT_MOUSE_BUTTON))
|
||||
{
|
||||
if (handleRectCenter.Contains(mousePosition))
|
||||
{
|
||||
currentId = id;
|
||||
handleOffset = position - mousePosition;
|
||||
initialMousePosition = mousePosition;
|
||||
axisConstraint = new HandleConstraint2D(1, 1);
|
||||
}
|
||||
else if (handleRectRight.Contains(mousePosition))
|
||||
{
|
||||
currentId = id;
|
||||
handleOffset = position - mousePosition;
|
||||
initialMousePosition = mousePosition;
|
||||
axisConstraint = new HandleConstraint2D(1, 0);
|
||||
}
|
||||
else if (handleRectUp.Contains(mousePosition))
|
||||
{
|
||||
currentId = id;
|
||||
handleOffset = position - mousePosition;
|
||||
initialMousePosition = mousePosition;
|
||||
axisConstraint = new HandleConstraint2D(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return scale;
|
||||
}
|
||||
|
||||
internal static void GetAllOverlapping(Vector2 mousePosition, List<GameObject> list)
|
||||
{
|
||||
list.Clear();
|
||||
|
||||
GameObject nearestGameObject = null;
|
||||
|
||||
do
|
||||
{
|
||||
nearestGameObject = HandleUtility.PickGameObject(mousePosition, false, list.ToArray());
|
||||
|
||||
if (nearestGameObject != null)
|
||||
list.Add(nearestGameObject);
|
||||
else
|
||||
break;
|
||||
}
|
||||
while (nearestGameObject != null);
|
||||
}
|
||||
|
||||
internal static void GetHovered(Vector2 mousePosition, List<GameObject> list)
|
||||
{
|
||||
list.Clear();
|
||||
var go = HandleUtility.PickGameObject(mousePosition, false);
|
||||
if (go != null)
|
||||
list.Add(go);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given two Vector2[] arrays, find the nearest two points within maxDelta and return the difference in offset.
|
||||
* @param points First Vector2[] array.
|
||||
* @param compare The Vector2[] array to compare @c points againts.
|
||||
* @mask If mask is not null, any index in mask will not be used in the compare array.
|
||||
* @param maxDelta The maximum distance for two points to be apart to be considered for nearness.
|
||||
* @notes This should probably use a divide and conquer algorithm instead of the O(n^2) approach (http://www.geeksforgeeks.org/closest-pair-of-points/)
|
||||
|
||||
*/
|
||||
public static bool NearestPointDelta(Vector2[] points, Vector2[] compare, int[] mask, float maxDelta, out Vector2 offset)
|
||||
{
|
||||
float dist = 0f;
|
||||
float minDist = maxDelta;
|
||||
bool foundMatch = false;
|
||||
offset = Vector2.zero;
|
||||
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
{
|
||||
for (int n = 0; n < compare.Length; n++)
|
||||
{
|
||||
if (points[i] == compare[n]) continue;
|
||||
|
||||
dist = Vector2.Distance(points[i], compare[n]);
|
||||
|
||||
if (dist < minDist)
|
||||
{
|
||||
if (mask != null && System.Array.IndexOf(mask, n) > -1)
|
||||
continue;
|
||||
|
||||
minDist = dist;
|
||||
offset = compare[n] - points[i];
|
||||
foundMatch = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the nearest point in the points array, or -1 if no point is within maxDelta range.
|
||||
*/
|
||||
public static int NearestPoint(Vector2 point, Vector2[] points, float maxDelta)
|
||||
{
|
||||
float dist = 0f;
|
||||
float minDist = maxDelta;
|
||||
int index = -1;
|
||||
|
||||
for (int i = 0; i < points.Length; i++)
|
||||
{
|
||||
if (point == points[i]) continue;
|
||||
|
||||
dist = Vector2.Distance(point, points[i]);
|
||||
|
||||
if (dist < minDist)
|
||||
{
|
||||
minDist = dist;
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pick the closest point on a world space set of line segments.
|
||||
/// Similar to UnityEditor.HandleUtility version except this also
|
||||
/// returns the index of the segment that best matched (source modified
|
||||
/// from UnityEngine.HandleUtility class).
|
||||
/// </summary>
|
||||
/// <param name="vertices"></param>
|
||||
/// <param name="index"></param>
|
||||
/// <param name="distanceToLine"></param>
|
||||
/// <param name="closeLoop"></param>
|
||||
/// <param name="trs"></param>
|
||||
/// <returns></returns>
|
||||
public static Vector3 ClosestPointToPolyLine(List<Vector3> vertices, out int index, out float distanceToLine, bool closeLoop = false, Transform trs = null)
|
||||
{
|
||||
distanceToLine = Mathf.Infinity;
|
||||
|
||||
if (trs != null)
|
||||
distanceToLine = HandleUtility.DistanceToLine(trs.TransformPoint(vertices[0]), trs.TransformPoint(vertices[1]));
|
||||
else
|
||||
distanceToLine = HandleUtility.DistanceToLine(vertices[0], vertices[1]);
|
||||
|
||||
index = 0;
|
||||
int count = vertices.Count;
|
||||
|
||||
for (int i = 2; i < (closeLoop ? count + 1 : count); i++)
|
||||
{
|
||||
var distance = 0f;
|
||||
|
||||
if (trs != null)
|
||||
distance = HandleUtility.DistanceToLine(trs.TransformPoint(vertices[i - 1]), trs.TransformPoint(vertices[i % count]));
|
||||
else
|
||||
distance = HandleUtility.DistanceToLine(vertices[i - 1], vertices[i % count]);
|
||||
|
||||
if (distance < distanceToLine)
|
||||
{
|
||||
distanceToLine = distance;
|
||||
index = i - 1;
|
||||
}
|
||||
}
|
||||
|
||||
Vector3 point_a = trs != null ? trs.TransformPoint(vertices[index]) : vertices[index];
|
||||
Vector3 point_b = trs != null ? trs.TransformPoint(vertices[(index + 1) % count]) : vertices[index + 1];
|
||||
|
||||
index++;
|
||||
|
||||
Vector2 gui_a = Event.current.mousePosition - HandleUtility.WorldToGUIPoint(point_a);
|
||||
Vector2 gui_b = HandleUtility.WorldToGUIPoint(point_b) - HandleUtility.WorldToGUIPoint(point_a);
|
||||
|
||||
float magnitude = gui_b.magnitude;
|
||||
float travel = Vector3.Dot(gui_b, gui_a);
|
||||
|
||||
if (magnitude > 1E-06f)
|
||||
travel /= magnitude * magnitude;
|
||||
|
||||
Vector3 p = Vector3.Lerp(point_a, point_b, Mathf.Clamp01(travel));
|
||||
|
||||
return trs != null ? trs.InverseTransformPoint(p) : p;
|
||||
}
|
||||
|
||||
internal static void PushMatrix()
|
||||
{
|
||||
s_HandleMatrix.Push(Handles.matrix);
|
||||
}
|
||||
|
||||
internal static void PopMatrix()
|
||||
{
|
||||
Handles.matrix = s_HandleMatrix.Pop();
|
||||
}
|
||||
|
||||
static Vector3 GetBitangent(Vector3 planeNormal)
|
||||
{
|
||||
var rhs = Vector3.forward;
|
||||
if(Mathf.Abs(Vector3.Dot(planeNormal, rhs)) > .9f)
|
||||
rhs = Vector3.right;
|
||||
|
||||
return Vector3.Cross(planeNormal, Vector3.Cross( rhs , planeNormal));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a plane suitable for mouse input in a scene view.
|
||||
/// </summary>
|
||||
/// <param name="mousePosition"></param>
|
||||
/// <returns></returns>
|
||||
internal static Plane FindBestPlane(Vector2 mousePosition)
|
||||
{
|
||||
var res = FindBestPlaneAndBitangent(mousePosition);
|
||||
return res.item1;
|
||||
}
|
||||
|
||||
internal static SimpleTuple<Plane, Vector3> FindBestPlaneAndBitangent(Vector2 mousePosition, GameObject objectToIgnore = null)
|
||||
{
|
||||
// Priority in finding the "best" plane for input from a mouse position:
|
||||
// 1. Take the plane from the first hit mesh.
|
||||
// 2. If ProGrids is drawing a grid, use the plane normal and raycast for position
|
||||
// 3. Use the nearest matching plane based on the scene snapping settings or else the scene camera direction
|
||||
|
||||
Plane plane;
|
||||
Vector3 bitangent;
|
||||
|
||||
if(GetPlaneFromPickedObject(mousePosition, objectToIgnore, out plane, out bitangent))
|
||||
return new SimpleTuple<Plane, Vector3>(plane, bitangent);
|
||||
|
||||
if (!GetPlaneFromProGridsAxis(mousePosition, out plane))
|
||||
plane = GetPlaneFromSceneView();
|
||||
|
||||
bitangent = GetBitangent(plane.normal);
|
||||
|
||||
return new SimpleTuple<Plane, Vector3>(plane, bitangent);
|
||||
}
|
||||
|
||||
static bool GetPlaneFromPickedObject(Vector2 mousePosition, GameObject objectToIgnore, out Plane plane, out Vector3 bitangent)
|
||||
{
|
||||
GameObject go = objectToIgnore;
|
||||
var ignorePicking = new List<GameObject>();
|
||||
|
||||
do
|
||||
{
|
||||
if (go != null)
|
||||
ignorePicking.Add(go);
|
||||
|
||||
go = HandleUtility.PickGameObject(mousePosition, false, ignorePicking.ToArray());
|
||||
} while (go != null && go.GetComponent<MeshFilter>() == null && go.GetComponent<Terrain>() == null);
|
||||
|
||||
if (go != null)
|
||||
{
|
||||
if (go.GetComponent<MeshFilter>() != null && go.GetComponent<MeshFilter>().sharedMesh != null)
|
||||
{
|
||||
RaycastHit hit;
|
||||
if (UnityEngine.ProBuilder.HandleUtility.MeshRaycast(
|
||||
HandleUtility.GUIPointToWorldRay(mousePosition),
|
||||
go,
|
||||
out hit))
|
||||
{
|
||||
plane = new Plane(
|
||||
go.transform.TransformDirection(hit.normal),
|
||||
go.transform.TransformPoint(hit.point));
|
||||
|
||||
bitangent = GetBitangent(plane.normal);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (go.GetComponent<Terrain>() != null)
|
||||
{
|
||||
UnityEngine.RaycastHit hit;
|
||||
if (Physics.Raycast(HandleUtility.GUIPointToWorldRay(mousePosition), out hit))
|
||||
{
|
||||
plane = new Plane(hit.normal, hit.point);
|
||||
var forward = go.transform.forward;
|
||||
bitangent = Mathf.Abs(Vector3.Dot(plane.normal, forward)) > .9f ? go.transform.up : forward;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plane = default(Plane);
|
||||
bitangent = Vector3.forward;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool GetPlaneFromProGridsAxis(Vector2 mousePosition, out Plane plane)
|
||||
{
|
||||
if (!ProGridsInterface.IsActive() || !ProGridsInterface.GridVisible())
|
||||
{
|
||||
plane = default(Plane);
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3 point = SceneView.lastActiveSceneView.pivot;
|
||||
Vector3 progridsPivot;
|
||||
if(ProGridsInterface.GetPivot(out progridsPivot))
|
||||
point = progridsPivot;
|
||||
Vector3 normal = Vector3.up;
|
||||
|
||||
if (ProGridsInterface.IsFullGridEnabled())
|
||||
{
|
||||
var ray = HandleUtility.GUIPointToWorldRay(mousePosition);
|
||||
|
||||
Plane[] planes = new Plane[3]
|
||||
{
|
||||
new Plane(Vector3.right, point),
|
||||
new Plane(Vector3.up, point),
|
||||
new Plane(Vector3.forward, point)
|
||||
};
|
||||
|
||||
float closestDistance = Mathf.Infinity;
|
||||
Plane closestPlane = default(Plane);
|
||||
|
||||
for (int i = 0; i < planes.Length; ++i)
|
||||
{
|
||||
float distance;
|
||||
|
||||
if (planes[i].Raycast(ray, out distance))
|
||||
{
|
||||
if (distance < closestDistance)
|
||||
{
|
||||
closestDistance = distance;
|
||||
closestPlane = planes[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plane = new Plane(closestPlane.normal, point);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
var axis = ProGridsInterface.GetActiveGridAxis();
|
||||
float offset = ProGridsInterface.GetActiveGridOffset();
|
||||
|
||||
switch (axis)
|
||||
{
|
||||
case HandleAxis.X:
|
||||
point.x += offset;
|
||||
normal = Vector3.right;
|
||||
break;
|
||||
|
||||
case HandleAxis.Y:
|
||||
point.y += offset;
|
||||
normal = Vector3.up;
|
||||
break;
|
||||
case HandleAxis.Z:
|
||||
point.z += offset;
|
||||
normal = Vector3.forward;
|
||||
break;
|
||||
}
|
||||
|
||||
plane = new Plane(normal, point);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static Plane GetPlaneFromSceneView()
|
||||
{
|
||||
Plane plane;
|
||||
Vector3 normal = Vector3.up;
|
||||
if(EditorSnapSettings.gridSnapActive || SceneView.lastActiveSceneView.showGrid)
|
||||
{
|
||||
var sceneView = SceneView.lastActiveSceneView;
|
||||
var cameraTransform = sceneView.camera.transform;
|
||||
var axis = sceneView.sceneViewGrids.gridAxis;
|
||||
var point = sceneView.sceneViewGrids.GetPivot(axis);
|
||||
|
||||
switch (axis)
|
||||
{
|
||||
case SceneViewGrid.GridRenderAxis.X:
|
||||
normal = Vector3.right;
|
||||
break;
|
||||
|
||||
case SceneViewGrid.GridRenderAxis.Y:
|
||||
normal = Vector3.up;
|
||||
break;
|
||||
case SceneViewGrid.GridRenderAxis.Z:
|
||||
normal = Vector3.forward;
|
||||
break;
|
||||
}
|
||||
|
||||
//Invert normal if camera if facing the other side of the plane
|
||||
if(Vector3.Dot(cameraTransform.forward, normal) > 0)
|
||||
normal *= -1f;
|
||||
|
||||
plane = new Plane(normal, point);
|
||||
|
||||
//If the camera if on the right side of the plane, return this plane
|
||||
if(plane.GetSide(cameraTransform.position))
|
||||
return plane;
|
||||
}
|
||||
|
||||
return GetPlaneFromCameraDirection();
|
||||
}
|
||||
|
||||
static Plane GetPlaneFromCameraDirection()
|
||||
{
|
||||
var sceneView = SceneView.lastActiveSceneView;
|
||||
var cameraDirection = sceneView.camera.transform.forward;
|
||||
|
||||
float pitch = Mathf.Abs(Vector3.Dot(cameraDirection, Vector3.up));
|
||||
float right = Mathf.Abs(Vector3.Dot(cameraDirection, Vector3.right));
|
||||
float forward = Mathf.Abs(Vector3.Dot(cameraDirection, Vector3.forward));
|
||||
|
||||
ProjectionAxis axis = ProjectionAxis.Y;
|
||||
|
||||
// Orthographic view, use X or Z
|
||||
if (pitch < .02f)
|
||||
{
|
||||
if (right > forward)
|
||||
axis = ProjectionAxis.X;
|
||||
else
|
||||
axis = ProjectionAxis.Z;
|
||||
}
|
||||
|
||||
return new Plane(Projection.ProjectionAxisToVector(axis), EditorSnapping.MoveSnap(SceneView.lastActiveSceneView.pivot));
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31b2d23ee6bb16a42bdc704a0b1df42c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
using UnityEditor.SettingsManagement;
|
||||
using UnityEngine;
|
||||
using UObject = UnityEngine.Object;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
static class EditorMaterialUtility
|
||||
{
|
||||
[UserSetting("Mesh Settings", "Material", "The default material to be applied to newly created shapes.")]
|
||||
static Pref<Material> s_DefaultMaterial = new Pref<Material>("mesh.userMaterial", null);
|
||||
|
||||
internal static Texture2D GetPreviewTexture(Material material)
|
||||
{
|
||||
if (material == null || material.shader == null)
|
||||
return null;
|
||||
|
||||
Texture2D best = null;
|
||||
|
||||
for (int i = 0; i < material.shader.GetPropertyCount(); i++)
|
||||
{
|
||||
if (material.shader.GetPropertyType(i) == UnityEngine.Rendering.ShaderPropertyType.Texture)
|
||||
{
|
||||
string propertyName = material.shader.GetPropertyName(i);
|
||||
|
||||
Texture2D tex = material.GetTexture(propertyName) as Texture2D;
|
||||
|
||||
if (tex != null)
|
||||
{
|
||||
if (propertyName.Contains("_MainTex") || propertyName.Contains("Albedo"))
|
||||
return tex;
|
||||
else if (best == null)
|
||||
best = tex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return best;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the material best matching the active mesh selection.
|
||||
/// </summary>
|
||||
internal static Material GetActiveSelection()
|
||||
{
|
||||
var mesh = MeshSelection.activeMesh;
|
||||
|
||||
if (mesh != null)
|
||||
{
|
||||
var face = MeshSelection.activeFace;
|
||||
|
||||
if (face == null)
|
||||
return mesh.renderer.sharedMaterial;
|
||||
|
||||
var sharedMaterials = mesh.renderer.sharedMaterials;
|
||||
|
||||
if (sharedMaterials != null)
|
||||
{
|
||||
var length = sharedMaterials.Length;
|
||||
return sharedMaterials[System.Math.Min(face.submeshIndex, length - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal static Material GetUserMaterial()
|
||||
{
|
||||
var mat = (Material)s_DefaultMaterial;
|
||||
if (mat != null)
|
||||
return mat;
|
||||
return BuiltinMaterials.defaultMaterial;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6832442ae76f440d9a8a532b98ebfc57
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.SettingsManagement;
|
||||
using UnityEngine.ProBuilder;
|
||||
using Math = UnityEngine.ProBuilder.Math;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains helper functions for mesh editing that are only available in the Editor.
|
||||
/// </summary>
|
||||
public static class EditorMeshUtility
|
||||
{
|
||||
const string k_MeshCacheDirectoryName = "ProBuilderMeshCache";
|
||||
static string k_MeshCacheDirectory = "Assets/ProBuilder Data/ProBuilderMeshCache";
|
||||
|
||||
[UserSetting("Mesh Editing", "Auto Resize Colliders", "Automatically resize colliders with mesh bounds as you edit.")]
|
||||
static Pref<bool> s_AutoResizeCollisions = new Pref<bool>("editor.autoRecalculateCollisions", false, SettingsScope.Project);
|
||||
|
||||
/// <summary>
|
||||
/// Raised after a ProBuilderMesh has been successfully optimized.
|
||||
/// </summary>
|
||||
/// <seealso cref="Optimize"/>
|
||||
public static event Action<ProBuilderMesh, Mesh> meshOptimized = null;
|
||||
|
||||
/// <summary>
|
||||
/// Optimizes the mesh geometry, and generates a UV2 channel if object is marked as <see cref="StaticEditorFlags.ContributeGI"/>
|
||||
/// or you set the `generateLightmapUVs` parameter to true.
|
||||
/// </summary>
|
||||
/// <remarks>This is only applicable to meshes with triangle topology. Quad meshes are not affected by this function.</remarks>
|
||||
/// <param name="mesh">The ProBuilder mesh component to optimize.</param>
|
||||
/// <param name="generateLightmapUVs">True to force ProBuilder to build UV2s if the Auto UV2 preference is disabled.</param>
|
||||
public static void Optimize(this ProBuilderMesh mesh, bool generateLightmapUVs = false)
|
||||
{
|
||||
if (mesh == null)
|
||||
throw new ArgumentNullException("mesh");
|
||||
|
||||
Mesh umesh = mesh.mesh;
|
||||
|
||||
if (umesh == null || umesh.vertexCount < 1)
|
||||
return;
|
||||
|
||||
bool skipMeshProcessing = false;
|
||||
|
||||
// @todo Support mesh compression for topologies other than Triangles.
|
||||
for (int i = 0; !skipMeshProcessing && i < umesh.subMeshCount; i++)
|
||||
if (umesh.GetTopology(i) != MeshTopology.Triangles)
|
||||
skipMeshProcessing = true;
|
||||
|
||||
#pragma warning disable 0219
|
||||
bool uv2Built = false;
|
||||
#pragma warning restore 0219
|
||||
if (!skipMeshProcessing)
|
||||
{
|
||||
bool autoLightmap = Lightmapping.autoUnwrapLightmapUV;
|
||||
bool lightmapUVs = generateLightmapUVs || (autoLightmap && mesh.gameObject.HasStaticFlag(StaticEditorFlags.ContributeGI));
|
||||
var usedInParticleSystem = UnityEngine.ProBuilder.MeshUtility.IsUsedInParticleSystem(mesh);
|
||||
|
||||
// if generating UV2, the process is to manually split the mesh into individual triangles,
|
||||
// generate uv2, then re-assemble with vertex collapsing where possible.
|
||||
// if not generating uv2, just collapse vertices.
|
||||
if (lightmapUVs)
|
||||
{
|
||||
Vertex[] vertices = UnityEngine.ProBuilder.MeshUtility.GeneratePerTriangleMesh(umesh);
|
||||
|
||||
float time = Time.realtimeSinceStartup;
|
||||
|
||||
UnwrapParam unwrap = Lightmapping.GetUnwrapParam(mesh.unwrapParameters);
|
||||
|
||||
Vector2[] uv2 = Unwrapping.GeneratePerTriangleUV(umesh, unwrap);
|
||||
|
||||
// If GenerateUV2() takes longer than 3 seconds (!), show a warning prompting user to disable auto-uv2 generation.
|
||||
if ((Time.realtimeSinceStartup - time) > 3f)
|
||||
Log.Warning(string.Format("Generate UV2 for \"{0}\" took {1} seconds! You may want to consider disabling Auto-UV2 generation in the `Preferences > ProBuilder` tab.", mesh.name, (Time.realtimeSinceStartup - time).ToString("F2")));
|
||||
|
||||
if (uv2.Length == vertices.Length)
|
||||
{
|
||||
for (int i = 0; i < uv2.Length; i++)
|
||||
vertices[i].uv2 = uv2[i];
|
||||
|
||||
uv2Built = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning("Generate UV2 failed. The returned size of UV2 array != mesh.vertexCount");
|
||||
}
|
||||
|
||||
UnityEngine.ProBuilder.MeshUtility.CollapseSharedVertices(umesh, vertices);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnityEngine.ProBuilder.MeshUtility.CollapseSharedVertices(umesh);
|
||||
}
|
||||
|
||||
if(usedInParticleSystem)
|
||||
UnityEngine.ProBuilder.MeshUtility.RestoreParticleSystem(mesh);
|
||||
}
|
||||
|
||||
if (s_AutoResizeCollisions)
|
||||
RebuildColliders(mesh);
|
||||
|
||||
if (meshOptimized != null)
|
||||
meshOptimized(mesh, umesh);
|
||||
|
||||
if (Experimental.meshesAreAssets)
|
||||
TryCacheMesh(mesh);
|
||||
|
||||
umesh.RecalculateUVDistributionMetric(0);
|
||||
if (uv2Built)
|
||||
umesh.RecalculateUVDistributionMetric(1);
|
||||
|
||||
UnityEditor.EditorUtility.SetDirty(mesh);
|
||||
}
|
||||
|
||||
internal static void TryCacheMesh(ProBuilderMesh pb)
|
||||
{
|
||||
Mesh mesh = pb.mesh;
|
||||
|
||||
// check for an existing mesh in the mesh cache and update or create a new one so
|
||||
// as not to clutter the scene yaml.
|
||||
string meshAssetPath = AssetDatabase.GetAssetPath(mesh);
|
||||
|
||||
// if mesh is already an asset any changes will already have been applied since
|
||||
// pb_Object is directly modifying the mesh asset
|
||||
if (string.IsNullOrEmpty(meshAssetPath))
|
||||
{
|
||||
// at the moment the asset_guid is only used to name the mesh something unique
|
||||
string guid = pb.assetGuid;
|
||||
|
||||
if (string.IsNullOrEmpty(guid))
|
||||
{
|
||||
guid = Guid.NewGuid().ToString("N");
|
||||
pb.assetGuid = guid;
|
||||
}
|
||||
|
||||
string meshCacheDirectory = GetMeshCacheDirectory(true);
|
||||
|
||||
string path = string.Format("{0}/{1}.asset", meshCacheDirectory, guid);
|
||||
|
||||
Mesh m = AssetDatabase.LoadAssetAtPath<Mesh>(path);
|
||||
|
||||
// a mesh already exists in the cache for this pb_Object
|
||||
if (m != null)
|
||||
{
|
||||
if (mesh != m)
|
||||
{
|
||||
// prefab instances should always point to the same mesh
|
||||
if (EditorUtility.IsPrefabInstance(pb.gameObject) || EditorUtility.IsPrefabAsset(pb.gameObject))
|
||||
{
|
||||
// Debug.Log("reconnect prefab to mesh");
|
||||
|
||||
// use the most recent mesh iteration (when undoing for example)
|
||||
UnityEngine.ProBuilder.MeshUtility.CopyTo(mesh, m);
|
||||
|
||||
UnityEngine.Object.DestroyImmediate(mesh);
|
||||
pb.gameObject.GetComponent<MeshFilter>().sharedMesh = m;
|
||||
|
||||
// also set the MeshCollider if it exists
|
||||
pb.Refresh(RefreshMask.Collisions);
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// duplicate mesh
|
||||
// Debug.Log("create new mesh in cache from disconnect");
|
||||
pb.assetGuid = Guid.NewGuid().ToString("N");
|
||||
path = string.Format("{0}/{1}.asset", meshCacheDirectory, pb.assetGuid);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("Mesh found in cache and scene mesh references match, but pb.asset_guid doesn't point to asset. Please report the circumstances leading to this event to Karl.");
|
||||
}
|
||||
}
|
||||
|
||||
AssetDatabase.CreateAsset(mesh, path);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool GetCachedMesh(ProBuilderMesh pb, out string path, out Mesh mesh)
|
||||
{
|
||||
if (pb.mesh != null)
|
||||
{
|
||||
string meshPath = AssetDatabase.GetAssetPath(pb.mesh);
|
||||
|
||||
if (!string.IsNullOrEmpty(meshPath))
|
||||
{
|
||||
path = meshPath;
|
||||
mesh = pb.mesh;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
string meshCacheDirectory = GetMeshCacheDirectory(false);
|
||||
string guid = pb.assetGuid;
|
||||
|
||||
path = string.Format("{0}/{1}.asset", meshCacheDirectory, guid);
|
||||
mesh = AssetDatabase.LoadAssetAtPath<Mesh>(path);
|
||||
|
||||
return mesh != null;
|
||||
}
|
||||
|
||||
static string GetMeshCacheDirectory(bool initializeIfMissing = false)
|
||||
{
|
||||
if (Directory.Exists(k_MeshCacheDirectory))
|
||||
return k_MeshCacheDirectory;
|
||||
|
||||
string[] results = Directory.GetDirectories("Assets", k_MeshCacheDirectoryName, SearchOption.AllDirectories);
|
||||
|
||||
if (results.Length < 1)
|
||||
{
|
||||
if (initializeIfMissing)
|
||||
{
|
||||
k_MeshCacheDirectory = FileUtility.GetLocalDataDirectory() + "/" + k_MeshCacheDirectoryName;
|
||||
Directory.CreateDirectory(k_MeshCacheDirectory);
|
||||
}
|
||||
else
|
||||
{
|
||||
k_MeshCacheDirectory = null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
k_MeshCacheDirectory = results.First();
|
||||
}
|
||||
|
||||
return k_MeshCacheDirectory;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resizes any collider components on this mesh to match the size of the mesh bounds.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh target to rebuild collider volumes for.</param>
|
||||
public static void RebuildColliders(this ProBuilderMesh mesh)
|
||||
{
|
||||
mesh.mesh.RecalculateBounds();
|
||||
|
||||
var bounds = mesh.mesh.bounds;
|
||||
|
||||
foreach (var collider in mesh.GetComponents<Collider>())
|
||||
{
|
||||
Type t = collider.GetType();
|
||||
|
||||
if (t == typeof(BoxCollider))
|
||||
{
|
||||
((BoxCollider)collider).center = bounds.center;
|
||||
((BoxCollider)collider).size = bounds.size;
|
||||
}
|
||||
else if (t == typeof(SphereCollider))
|
||||
{
|
||||
((SphereCollider)collider).center = bounds.center;
|
||||
((SphereCollider)collider).radius = Math.LargestValue(bounds.extents);
|
||||
}
|
||||
else if (t == typeof(CapsuleCollider))
|
||||
{
|
||||
((CapsuleCollider)collider).center = bounds.center;
|
||||
Vector2 xy = new Vector2(bounds.extents.x, bounds.extents.z);
|
||||
((CapsuleCollider)collider).radius = Math.LargestValue(xy);
|
||||
((CapsuleCollider)collider).height = bounds.size.y;
|
||||
}
|
||||
else if (t == typeof(WheelCollider))
|
||||
{
|
||||
((WheelCollider)collider).center = bounds.center;
|
||||
((WheelCollider)collider).radius = Math.LargestValue(bounds.extents);
|
||||
}
|
||||
else if (t == typeof(MeshCollider))
|
||||
{
|
||||
mesh.Refresh(RefreshMask.Collisions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c3adccdd46845df47a5d11429dac1f82
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
static class EditorPathSelectionUtility
|
||||
{
|
||||
const string k_ShortcutId = "ProBuilder/Selection/Select Path";
|
||||
|
||||
[InitializeOnLoadMethod]
|
||||
static void Init()
|
||||
{
|
||||
ShortcutManager.instance.shortcutBindingChanged += ValidateShortcutBinding;
|
||||
}
|
||||
|
||||
static void ValidateShortcutBinding(ShortcutBindingChangedEventArgs args)
|
||||
{
|
||||
if (args.shortcutId.Equals(k_ShortcutId))
|
||||
{
|
||||
var newCombination = args.newBinding.keyCombinationSequence.First();
|
||||
if(newCombination.modifiers.Equals(ShortcutModifiers.None))
|
||||
Debug.LogWarning("The shortcut 'ProBuilder/Selection/Select Path' must have at least one modifier, otherwise path selection will not work.");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsSelectionPathModifier(EventModifiers em)
|
||||
{
|
||||
var binding = ShortcutManager.instance.GetShortcutBinding(k_ShortcutId);
|
||||
var combination = binding.keyCombinationSequence.First();
|
||||
|
||||
//At least one modifier is required for the path selection
|
||||
if (combination.modifiers.Equals(ShortcutModifiers.None))
|
||||
return false;
|
||||
|
||||
// Check if all desired modifiers are pressed
|
||||
if (combination.modifiers.HasFlag(ShortcutModifiers.Shift) && (em & EventModifiers.Shift) != EventModifiers.Shift)
|
||||
return false;
|
||||
#if UNITY_EDITOR_OSX
|
||||
if (combination.modifiers.HasFlag(ShortcutModifiers.Control) && (em & EventModifiers.Control) != EventModifiers.Control)
|
||||
return false;
|
||||
if (combination.modifiers.HasFlag(ShortcutModifiers.Action) && (em & EventModifiers.Command) != EventModifiers.Command)
|
||||
return false;
|
||||
#else
|
||||
if ((combination.modifiers.HasFlag(ShortcutModifiers.Control) || combination.modifiers.HasFlag(ShortcutModifiers.Action))
|
||||
&& (em & EventModifiers.Control) != EventModifiers.Control)
|
||||
return false;
|
||||
#endif
|
||||
if (combination.modifiers.HasFlag(ShortcutModifiers.Alt) && (em & EventModifiers.Alt) != EventModifiers.Alt)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[Shortcut("ProBuilder/Selection/Select Path", typeof(PositionToolContext.ProBuilderShortcutContext), KeyCode.Mouse0, ShortcutModifiers.Shift | ShortcutModifiers.Action)]
|
||||
static void DoPathSelection(ShortcutArguments args)
|
||||
{
|
||||
var mesh = EditorSceneViewPicker.selection?.mesh;
|
||||
if (mesh == null || ProBuilderEditor.selectMode != SelectMode.Face)
|
||||
return;
|
||||
|
||||
var activeFace = mesh.GetActiveFace();
|
||||
if (activeFace != null)
|
||||
{
|
||||
var faces = mesh.facesInternal;
|
||||
var face = ProBuilderEditor.instance.hovering.faces[0];
|
||||
|
||||
UndoUtility.RecordSelection(mesh, "Select Face");
|
||||
var pathFaces = SelectPathFaces.GetPath(mesh, Array.IndexOf<Face>(faces, activeFace), Array.IndexOf<Face>(faces, face));
|
||||
if (pathFaces != null)
|
||||
{
|
||||
foreach (var pathFace in pathFaces)
|
||||
mesh.AddToFaceSelection(pathFace);
|
||||
|
||||
Event.current.Use();
|
||||
ProBuilderEditor.instance.ResetSceneGUIEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e70a4b45c6cf44c4a63890d71d7cca97
|
||||
timeCreated: 1707334475
|
||||
+944
@@ -0,0 +1,944 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
using PHandleUtility = UnityEngine.ProBuilder.HandleUtility;
|
||||
using UHandleUtility = UnityEditor.HandleUtility;
|
||||
using RaycastHit = UnityEngine.ProBuilder.RaycastHit;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
struct ScenePickerPreferences
|
||||
{
|
||||
public const float maxPointerDistance = 20f;
|
||||
public const float offPointerMultiplier = 1.2f;
|
||||
|
||||
public CullingMode cullMode;
|
||||
public RectSelectMode rectSelectMode;
|
||||
}
|
||||
|
||||
static class EditorSceneViewPicker
|
||||
{
|
||||
static int s_DeepSelectionPrevious = 0x0;
|
||||
static bool s_AppendModifierPreviousState = false;
|
||||
static SceneSelection s_Selection = new SceneSelection();
|
||||
internal static SceneSelection selection => s_Selection;
|
||||
static List<VertexPickerEntry> s_NearestVertices = new List<VertexPickerEntry>();
|
||||
static List<GameObject> s_OverlappingGameObjects = new List<GameObject>();
|
||||
static readonly List<int> s_IndexBuffer = new List<int>(16);
|
||||
static List<Edge> s_EdgeBuffer = new List<Edge>(32);
|
||||
|
||||
// When enabled, a mouse click on an unselected mesh will select both the GameObject and the mesh element picked.
|
||||
const bool k_AllowUnselected = true;
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_DeepSelectionPrevious = 0x0;
|
||||
s_AppendModifierPreviousState = false;
|
||||
s_Selection.Clear();
|
||||
s_NearestVertices.Clear();
|
||||
s_OverlappingGameObjects.Clear();
|
||||
s_IndexBuffer.Clear();
|
||||
s_EdgeBuffer.Clear();
|
||||
}
|
||||
|
||||
public static void DoMouseHover(SceneSelection selection)
|
||||
{
|
||||
if (selection.faces.Count == 0)
|
||||
return;
|
||||
var mesh = selection.mesh;
|
||||
var face = selection.faces[0];
|
||||
var activeFace = mesh.GetActiveFace();
|
||||
if (activeFace == null || activeFace == face)
|
||||
return;
|
||||
|
||||
var faces = mesh.facesInternal;
|
||||
var pathFaces = SelectPathFaces.GetPath(mesh, Array.IndexOf<Face>(faces, activeFace), Array.IndexOf<Face>(faces, face));
|
||||
|
||||
if (pathFaces != null)
|
||||
{
|
||||
foreach (var path in pathFaces)
|
||||
selection.faces.Add(faces[path]);
|
||||
}
|
||||
}
|
||||
|
||||
public static ProBuilderMesh DoMouseClick(Event evt, SelectMode selectionMode, ScenePickerPreferences pickerPreferences)
|
||||
{
|
||||
bool appendModifier = EditorHandleUtility.IsAppendModifier(evt.modifiers);
|
||||
bool addToSelectionModifier = EditorHandleUtility.IsSelectionAddModifier(evt.modifiers);
|
||||
bool addOrRemoveIfPresentFromSelectionModifier = EditorHandleUtility.IsSelectionAppendOrRemoveIfPresentModifier(evt.modifiers);
|
||||
|
||||
float pickedElementDistance;
|
||||
|
||||
if (selectionMode.ContainsFlag(SelectMode.Edge | SelectMode.TextureEdge))
|
||||
pickedElementDistance = EdgeRaycast(evt.mousePosition, pickerPreferences, k_AllowUnselected, s_Selection);
|
||||
else if (selectionMode.ContainsFlag(SelectMode.Vertex | SelectMode.TextureVertex))
|
||||
pickedElementDistance = VertexRaycast(evt.mousePosition, pickerPreferences, k_AllowUnselected, s_Selection);
|
||||
else
|
||||
pickedElementDistance = FaceRaycast(evt.mousePosition, pickerPreferences, k_AllowUnselected, s_Selection, evt.clickCount > 1 ? -1 : 0, false);
|
||||
|
||||
evt.Use();
|
||||
|
||||
var isProBuilderMesh = s_Selection.gameObject != null && s_Selection.gameObject.GetComponent<ProBuilderMesh>() != null;
|
||||
|
||||
if (!appendModifier)
|
||||
{
|
||||
if(s_Selection.mesh != null)
|
||||
s_Selection.mesh.ClearSelection();
|
||||
if (s_Selection.gameObject == null || !isProBuilderMesh)
|
||||
{
|
||||
// Don't clear object selection if we are in the PB Context, just clear sub-elements selection
|
||||
MeshSelection.ClearElementSelection();
|
||||
}
|
||||
else
|
||||
MeshSelection.SetSelection((GameObject)null);
|
||||
}
|
||||
|
||||
if (!isProBuilderMesh)
|
||||
return null;
|
||||
|
||||
if (pickedElementDistance > ScenePickerPreferences.maxPointerDistance)
|
||||
{
|
||||
if (appendModifier && Selection.gameObjects.Contains(s_Selection.gameObject))
|
||||
MeshSelection.RemoveFromSelection(s_Selection.gameObject);
|
||||
else
|
||||
MeshSelection.AddToSelection(s_Selection.gameObject);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
GameObject candidateNewActiveObject = s_Selection.gameObject;
|
||||
bool activeObjectSelectionChanged = Selection.gameObjects.Contains(s_Selection.gameObject) && s_Selection.gameObject != Selection.activeGameObject;
|
||||
|
||||
if (s_Selection.mesh != null)
|
||||
{
|
||||
var mesh = s_Selection.mesh;
|
||||
|
||||
foreach (var face in s_Selection.faces)
|
||||
{
|
||||
var faces = mesh.faces as Face[] ?? mesh.faces.ToArray();
|
||||
var ind = Array.IndexOf<Face>(faces, face);
|
||||
var sel = mesh.selectedFaceIndexes.IndexOf(ind);
|
||||
|
||||
UndoUtility.RecordSelection(mesh, "Select Face");
|
||||
if (sel > -1)
|
||||
{
|
||||
if (!appendModifier || addOrRemoveIfPresentFromSelectionModifier ||
|
||||
(addToSelectionModifier && face == mesh.GetActiveFace() && !activeObjectSelectionChanged))
|
||||
{
|
||||
mesh.RemoveFromFaceSelectionAtIndex(sel);
|
||||
|
||||
if (addOrRemoveIfPresentFromSelectionModifier && activeObjectSelectionChanged)
|
||||
{
|
||||
candidateNewActiveObject = Selection.activeGameObject;
|
||||
}
|
||||
else if (mesh.selectedFaceCount == 0)
|
||||
{
|
||||
for (var i = MeshSelection.topInternal.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (MeshSelection.topInternal[i].selectedFaceCount > 0)
|
||||
{
|
||||
candidateNewActiveObject = MeshSelection.topInternal[i].gameObject;
|
||||
activeObjectSelectionChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mesh.selectedFaceIndicesInternal = mesh.selectedFaceIndicesInternal.Remove(ind);
|
||||
|
||||
mesh.SetSelectedFaces(mesh.selectedFaceIndicesInternal.Add(ind));
|
||||
}
|
||||
}
|
||||
else
|
||||
mesh.AddToFaceSelection(ind);
|
||||
}
|
||||
|
||||
foreach(var edge in s_Selection.edges)
|
||||
{
|
||||
int ind = mesh.IndexOf(mesh.selectedEdges, edge);
|
||||
|
||||
UndoUtility.RecordSelection(mesh, "Select Edge");
|
||||
|
||||
if (ind > -1)
|
||||
{
|
||||
if (!appendModifier || addOrRemoveIfPresentFromSelectionModifier ||
|
||||
(addToSelectionModifier && edge == mesh.GetActiveEdge() && !activeObjectSelectionChanged))
|
||||
{
|
||||
mesh.SetSelectedEdges(mesh.selectedEdges.ToArray().RemoveAt(ind));
|
||||
|
||||
if (addOrRemoveIfPresentFromSelectionModifier && activeObjectSelectionChanged)
|
||||
{
|
||||
candidateNewActiveObject = Selection.activeGameObject;
|
||||
}
|
||||
else if (mesh.selectedEdgeCount == 0)
|
||||
{
|
||||
for (var i = MeshSelection.topInternal.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (MeshSelection.topInternal[i].selectedEdgeCount > 0)
|
||||
{
|
||||
candidateNewActiveObject = MeshSelection.topInternal[i].gameObject;
|
||||
activeObjectSelectionChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mesh.selectedEdgesInternal = mesh.selectedEdgesInternal.Remove(edge);
|
||||
mesh.SetSelectedEdges(mesh.selectedEdgesInternal.Add(edge));
|
||||
}
|
||||
}
|
||||
else
|
||||
mesh.SetSelectedEdges(mesh.selectedEdges.ToArray().Add(edge));
|
||||
}
|
||||
foreach(var vertex in s_Selection.vertexes)
|
||||
{
|
||||
int ind = Array.IndexOf(mesh.selectedIndexesInternal, vertex);
|
||||
|
||||
UndoUtility.RecordSelection(mesh, "Select Vertex");
|
||||
|
||||
if (ind > -1)
|
||||
{
|
||||
var sharedIndex = mesh.sharedVertexLookup[vertex];
|
||||
var sharedVertex = mesh.sharedVerticesInternal[sharedIndex];
|
||||
s_IndexBuffer.Clear();
|
||||
foreach (var sVertex in sharedVertex)
|
||||
{
|
||||
var index = Array.IndexOf(mesh.selectedIndexesInternal, sVertex);
|
||||
if (index < 0)
|
||||
continue;
|
||||
|
||||
s_IndexBuffer.Add(index);
|
||||
}
|
||||
s_IndexBuffer.Sort();
|
||||
|
||||
if (!appendModifier || addOrRemoveIfPresentFromSelectionModifier ||
|
||||
(addToSelectionModifier && vertex == mesh.GetActiveVertex() && !activeObjectSelectionChanged))
|
||||
{
|
||||
mesh.selectedIndexesInternal = mesh.selectedIndexesInternal.SortedRemoveAt(s_IndexBuffer);
|
||||
mesh.SetSelectedVertices(mesh.selectedIndexesInternal);
|
||||
|
||||
if (addOrRemoveIfPresentFromSelectionModifier && activeObjectSelectionChanged)
|
||||
{
|
||||
candidateNewActiveObject = Selection.activeGameObject;
|
||||
}
|
||||
else if (mesh.selectedIndexesInternal.Length == 0)
|
||||
{
|
||||
for (var i = MeshSelection.topInternal.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (MeshSelection.topInternal[i].selectedIndexesInternal.Length > 0)
|
||||
{
|
||||
candidateNewActiveObject = MeshSelection.topInternal[i].gameObject;
|
||||
activeObjectSelectionChanged = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
mesh.selectedIndexesInternal = mesh.selectedIndexesInternal.SortedRemoveAt(s_IndexBuffer);
|
||||
mesh.SetSelectedVertices(mesh.selectedIndexesInternal.Add(vertex));
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
mesh.SetSelectedVertices(mesh.selectedIndexesInternal.Add(vertex));
|
||||
}
|
||||
|
||||
if(activeObjectSelectionChanged)
|
||||
MeshSelection.MakeActiveObject(candidateNewActiveObject);
|
||||
else
|
||||
MeshSelection.AddToSelection(candidateNewActiveObject);
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void DoMouseDrag(Rect mouseDragRect, SelectMode selectionMode, ScenePickerPreferences scenePickerPreferences)
|
||||
{
|
||||
var pickingOptions = new PickerOptions()
|
||||
{
|
||||
depthTest = scenePickerPreferences.cullMode == CullingMode.Back,
|
||||
rectSelectMode = scenePickerPreferences.rectSelectMode
|
||||
};
|
||||
|
||||
UndoUtility.RecordSelection("Drag Select");
|
||||
bool isSelectionAddModifier = EditorHandleUtility.IsSelectionAddModifier(Event.current.modifiers);
|
||||
bool isSelectionRemoveModifier = EditorHandleUtility.IsSelectionAppendOrRemoveIfPresentModifier(Event.current.modifiers);
|
||||
bool isAppendModifier = isSelectionAddModifier || isSelectionRemoveModifier;
|
||||
|
||||
if (!isAppendModifier)
|
||||
MeshSelection.ClearElementSelection();
|
||||
|
||||
bool elementsInDragRect = false;
|
||||
|
||||
switch (selectionMode)
|
||||
{
|
||||
case SelectMode.Vertex:
|
||||
case SelectMode.TextureVertex:
|
||||
{
|
||||
var lastSceneView = SceneView.lastActiveSceneView;
|
||||
var isDrawingGizmos = lastSceneView.drawGizmos;
|
||||
lastSceneView.drawGizmos = false;
|
||||
Dictionary<ProBuilderMesh, HashSet<int>> selected = SelectionPicker.PickVerticesInRect(
|
||||
lastSceneView.camera,
|
||||
mouseDragRect,
|
||||
MeshSelection.topInternal,
|
||||
pickingOptions,
|
||||
EditorGUIUtility.pixelsPerPoint);
|
||||
lastSceneView.drawGizmos = isDrawingGizmos;
|
||||
|
||||
foreach (var kvp in selected)
|
||||
{
|
||||
var mesh = kvp.Key;
|
||||
SharedVertex[] sharedIndexes = mesh.sharedVerticesInternal;
|
||||
HashSet<int> common;
|
||||
|
||||
if (isAppendModifier)
|
||||
{
|
||||
common = mesh.GetSharedVertexHandles(mesh.selectedIndexesInternal);
|
||||
|
||||
if(isSelectionAddModifier)
|
||||
common.UnionWith(kvp.Value);
|
||||
else if(isSelectionRemoveModifier)
|
||||
common.RemoveWhere(x => kvp.Value.Contains(x));
|
||||
}
|
||||
else
|
||||
{
|
||||
common = kvp.Value;
|
||||
}
|
||||
|
||||
elementsInDragRect |= kvp.Value.Count > 0;
|
||||
mesh.SetSelectedVertices(common.SelectMany(x => sharedIndexes[x]));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SelectMode.Face:
|
||||
case SelectMode.TextureFace:
|
||||
{
|
||||
var lastSceneView = SceneView.lastActiveSceneView;
|
||||
var isDrawingGizmos = lastSceneView.drawGizmos;
|
||||
lastSceneView.drawGizmos = false;
|
||||
Dictionary<ProBuilderMesh, HashSet<Face>> selected = SelectionPicker.PickFacesInRect(
|
||||
lastSceneView.camera,
|
||||
mouseDragRect,
|
||||
MeshSelection.topInternal,
|
||||
pickingOptions,
|
||||
EditorGUIUtility.pixelsPerPoint);
|
||||
lastSceneView.drawGizmos = isDrawingGizmos;
|
||||
|
||||
foreach (var kvp in selected)
|
||||
{
|
||||
HashSet<Face> current;
|
||||
|
||||
if (isAppendModifier)
|
||||
{
|
||||
current = new HashSet<Face>(kvp.Key.selectedFacesInternal);
|
||||
|
||||
if(isSelectionAddModifier)
|
||||
current.UnionWith(kvp.Value);
|
||||
else if(isSelectionRemoveModifier)
|
||||
current.RemoveWhere(x => kvp.Value.Contains(x));
|
||||
}
|
||||
else
|
||||
{
|
||||
current = kvp.Value;
|
||||
}
|
||||
|
||||
elementsInDragRect |= kvp.Value.Count > 0;
|
||||
kvp.Key.SetSelectedFaces(current);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SelectMode.Edge:
|
||||
case SelectMode.TextureEdge:
|
||||
{
|
||||
var lastSceneView = SceneView.lastActiveSceneView;
|
||||
var isDrawingGizmos = lastSceneView.drawGizmos;
|
||||
lastSceneView.drawGizmos = false;
|
||||
var selected = SelectionPicker.PickEdgesInRect(
|
||||
lastSceneView.camera,
|
||||
mouseDragRect,
|
||||
MeshSelection.topInternal,
|
||||
pickingOptions,
|
||||
EditorGUIUtility.pixelsPerPoint);
|
||||
lastSceneView.drawGizmos = isDrawingGizmos;
|
||||
|
||||
foreach (var kvp in selected)
|
||||
{
|
||||
ProBuilderMesh mesh = kvp.Key;
|
||||
Dictionary<int, int> common = mesh.sharedVertexLookup;
|
||||
HashSet<EdgeLookup> selectedEdges = EdgeLookup.GetEdgeLookupHashSet(kvp.Value, common);
|
||||
HashSet<EdgeLookup> current;
|
||||
|
||||
if (isAppendModifier)
|
||||
{
|
||||
current = EdgeLookup.GetEdgeLookupHashSet(mesh.selectedEdges, common);
|
||||
|
||||
if(isSelectionAddModifier)
|
||||
current.UnionWith(selectedEdges);
|
||||
else if(isSelectionRemoveModifier)
|
||||
current.RemoveWhere(x => selectedEdges.Contains(x));
|
||||
}
|
||||
else
|
||||
{
|
||||
current = selectedEdges;
|
||||
}
|
||||
|
||||
elementsInDragRect |= kvp.Value.Count > 0;
|
||||
mesh.SetSelectedEdges(current.Select(x => x.local));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ProBuilderEditor.Refresh();
|
||||
SceneView.RepaintAll();
|
||||
}
|
||||
|
||||
// Get the object & mesh selection that the mouse is currently nearest.
|
||||
// A ProBuilderMesh is returned because double click actions need to know what the last selected pb_Object was.
|
||||
// If deepClickOffset is specified, the object + deepClickOffset in the deep select stack will be returned (instead of next).
|
||||
internal static float MouseRayHitTest(
|
||||
Vector3 mousePosition,
|
||||
SelectMode selectionMode,
|
||||
ScenePickerPreferences pickerOptions,
|
||||
SceneSelection selection,
|
||||
bool allowUnselected = false)
|
||||
{
|
||||
if (selectionMode.ContainsFlag(SelectMode.Edge | SelectMode.TextureEdge))
|
||||
return EdgeRaycast(mousePosition, pickerOptions, allowUnselected, selection);
|
||||
|
||||
if (selectionMode.ContainsFlag(SelectMode.Vertex | SelectMode.TextureVertex))
|
||||
return VertexRaycast(mousePosition, pickerOptions, allowUnselected, selection);
|
||||
|
||||
return FaceRaycast(mousePosition, pickerOptions, allowUnselected, selection, 0, true);
|
||||
}
|
||||
|
||||
static List<(ProBuilderMesh mesh, Face face, float dist, int hash)> s_PbHits = new List<(ProBuilderMesh, Face, float, int)>();
|
||||
|
||||
static float FaceRaycast(Vector3 mousePosition,
|
||||
ScenePickerPreferences pickerOptions,
|
||||
bool allowUnselected,
|
||||
SceneSelection selection,
|
||||
int deepClickOffset = 0,
|
||||
bool isPreview = true)
|
||||
{
|
||||
GameObject candidateGo = null;
|
||||
ProBuilderMesh candidatePb = null;
|
||||
Face candidateFace = null;
|
||||
float candidateDistance = Mathf.Infinity;
|
||||
float resultDistance = Mathf.Infinity;
|
||||
|
||||
// If any event modifiers are engaged don't cycle the deep click
|
||||
EventModifiers em = EventModifiers.None;
|
||||
if (Event.current != null)
|
||||
em = Event.current.modifiers;
|
||||
|
||||
// Reset cycle if we used an event modifier previously.
|
||||
// Move state back to single selection.
|
||||
if ((em != EventModifiers.None) != s_AppendModifierPreviousState)
|
||||
{
|
||||
s_AppendModifierPreviousState = (em != EventModifiers.None);
|
||||
s_DeepSelectionPrevious = 0;
|
||||
}
|
||||
|
||||
if (isPreview || em != EventModifiers.None)
|
||||
EditorHandleUtility.GetHovered(mousePosition, s_OverlappingGameObjects);
|
||||
else
|
||||
EditorHandleUtility.GetAllOverlapping(mousePosition, s_OverlappingGameObjects);
|
||||
|
||||
selection.Clear();
|
||||
|
||||
// First, find all ProBuilder meshes and their hit faces under the mouse
|
||||
for (int i = 0, pickedCount = s_OverlappingGameObjects.Count; i < pickedCount; i++)
|
||||
{
|
||||
var go = s_OverlappingGameObjects[i];
|
||||
var mesh = go.GetComponent<ProBuilderMesh>();
|
||||
|
||||
if (mesh != null && (allowUnselected || MeshSelection.topInternal.Contains(mesh)))
|
||||
{
|
||||
Ray ray = UHandleUtility.GUIPointToWorldRay(mousePosition);
|
||||
RaycastHit hit;
|
||||
|
||||
if (UnityEngine.ProBuilder.HandleUtility.FaceRaycast(ray,
|
||||
mesh,
|
||||
out hit,
|
||||
Mathf.Infinity,
|
||||
pickerOptions.cullMode))
|
||||
{
|
||||
Face face = mesh.facesInternal[hit.face];
|
||||
float dist = Vector2.SqrMagnitude(((Vector2)mousePosition) - HandleUtility.WorldToGUIPoint(mesh.transform.TransformPoint(hit.point)));
|
||||
s_PbHits.Add((mesh, face, dist, face.GetHashCode()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s_PbHits.Count > 0)
|
||||
{
|
||||
int chosenIndex = 0;
|
||||
|
||||
// Apply deep click cycling logic only if it's an actual click and a previous selection exists
|
||||
if (!isPreview && s_DeepSelectionPrevious != 0)
|
||||
{
|
||||
int currentSelectionIndex = -1;
|
||||
// Find the index of the previously selected item (if it's still in the list)
|
||||
for (int i = 0; i < s_PbHits.Count; i++)
|
||||
{
|
||||
if (s_PbHits[i].hash == s_DeepSelectionPrevious)
|
||||
{
|
||||
currentSelectionIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentSelectionIndex != -1)
|
||||
{
|
||||
// Calculate the next index for cycling
|
||||
chosenIndex = (currentSelectionIndex + (1 + deepClickOffset)) % s_PbHits.Count;
|
||||
// Handle negative result from modulo for deepClickOffset = -1 if currentSelectionIndex is 0
|
||||
if (chosenIndex < 0) chosenIndex += s_PbHits.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the mouse moved enough that none of the hit faces correspond to the previous selection, we reset
|
||||
s_DeepSelectionPrevious = 0;
|
||||
}
|
||||
// If s_DeepSelectionPrevious was set but no matching PB hit is found in current list,
|
||||
// fall back to the closest one (chosenIndex remains 0)
|
||||
}
|
||||
// else for first click or if s_DeepSelectionPrevious is 0, chosenIndex remains 0 (selects closest)
|
||||
|
||||
var selectedHit = s_PbHits[chosenIndex];
|
||||
candidateGo = selectedHit.mesh.gameObject;
|
||||
candidatePb = selectedHit.mesh;
|
||||
candidateFace = selectedHit.face;
|
||||
candidateDistance = selectedHit.dist;
|
||||
|
||||
// Update s_DeepSelectionPrevious only for actual clicks after cycling/filtering (not hovers)
|
||||
if (!isPreview)
|
||||
{
|
||||
s_DeepSelectionPrevious = selectedHit.hash;
|
||||
}
|
||||
}
|
||||
|
||||
// Final selection update
|
||||
if (candidateGo != null)
|
||||
{
|
||||
selection.gameObject = candidateGo;
|
||||
resultDistance = Mathf.Sqrt(candidateDistance);
|
||||
|
||||
if (candidatePb != null)
|
||||
{
|
||||
if (candidatePb.selectable)
|
||||
{
|
||||
selection.mesh = candidatePb;
|
||||
selection.SetSingleFace(candidateFace);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (candidateGo == null && !isPreview)
|
||||
{
|
||||
// if we click somewhere with no PB, we reset the deep cycle
|
||||
s_DeepSelectionPrevious = 0;
|
||||
}
|
||||
|
||||
s_PbHits.Clear();
|
||||
return resultDistance;
|
||||
}
|
||||
|
||||
static float VertexRaycast(Vector3 mousePosition, ScenePickerPreferences pickerOptions, bool allowUnselected, SceneSelection selection)
|
||||
{
|
||||
Camera cam = SceneView.lastActiveSceneView.camera;
|
||||
selection.Clear();
|
||||
s_NearestVertices.Clear();
|
||||
bool highlightedVertexExists = ProBuilderEditor.instance.hovering.vertexes.Count > 0;
|
||||
|
||||
// if there is a vertex already highlighted, we don't want to select a different game object
|
||||
if (!highlightedVertexExists)
|
||||
{
|
||||
selection.gameObject = HandleUtility.PickGameObject(mousePosition, false);
|
||||
}
|
||||
float maxDistance = ScenePickerPreferences.maxPointerDistance * ScenePickerPreferences.maxPointerDistance;
|
||||
ProBuilderMesh hoveredMesh = selection.gameObject != null ? selection.gameObject.GetComponent<ProBuilderMesh>() : null;
|
||||
|
||||
if (allowUnselected && selection.gameObject != null)
|
||||
{
|
||||
if (hoveredMesh != null && hoveredMesh.selectable && !MeshSelection.Contains(hoveredMesh))
|
||||
{
|
||||
GetNearestVertices(hoveredMesh, mousePosition, s_NearestVertices, maxDistance, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (selection.mesh == null)
|
||||
{
|
||||
foreach (var mesh in MeshSelection.topInternal)
|
||||
{
|
||||
if (!mesh.selectable)
|
||||
continue;
|
||||
|
||||
GetNearestVertices(mesh, mousePosition, s_NearestVertices, maxDistance, hoveredMesh == mesh || hoveredMesh == null ? 1.0f : ScenePickerPreferences.offPointerMultiplier);
|
||||
}
|
||||
}
|
||||
|
||||
s_NearestVertices.Sort((x, y) => x.screenDistance.CompareTo(y.screenDistance));
|
||||
|
||||
for (int i = 0; i < s_NearestVertices.Count; i++)
|
||||
{
|
||||
if (!UnityEngine.ProBuilder.HandleUtility.PointIsOccluded(cam, s_NearestVertices[i].mesh, s_NearestVertices[i].worldPosition))
|
||||
{
|
||||
selection.gameObject = s_NearestVertices[i].mesh.gameObject;
|
||||
selection.mesh = s_NearestVertices[i].mesh;
|
||||
selection.SetSingleVertex(s_NearestVertices[i].vertex);
|
||||
|
||||
return Mathf.Sqrt(s_NearestVertices[i].screenDistance);
|
||||
}
|
||||
}
|
||||
|
||||
return Mathf.Infinity;
|
||||
}
|
||||
|
||||
static int GetNearestVertices(ProBuilderMesh mesh, Vector3 mousePosition, List<VertexPickerEntry> list, float maxDistance, float distModifier)
|
||||
{
|
||||
var positions = mesh.positionsInternal;
|
||||
var common = mesh.sharedVerticesInternal;
|
||||
var matches = 0;
|
||||
|
||||
for (int n = 0, c = common.Length; n < c; n++)
|
||||
{
|
||||
int index = common[n][0];
|
||||
Vector3 v = mesh.transform.TransformPoint(positions[index]);
|
||||
Vector3 p = UHandleUtility.WorldToGUIPoint(v);
|
||||
|
||||
float dist = (p - mousePosition).sqrMagnitude * distModifier;
|
||||
|
||||
if (dist < maxDistance)
|
||||
{
|
||||
list.Add(new VertexPickerEntry()
|
||||
{
|
||||
mesh = mesh,
|
||||
screenDistance = dist,
|
||||
worldPosition = v,
|
||||
vertex = index
|
||||
});
|
||||
|
||||
matches++;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the nearest <see cref="Edge"/> to a screen position.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Distance is returned as the screen distance to mesh, not edge.
|
||||
/// </returns>
|
||||
static float EdgeRaycast(Vector3 mousePosition, ScenePickerPreferences pickerPrefs, bool allowUnselected, SceneSelection selection)
|
||||
{
|
||||
selection.Clear();
|
||||
bool highlightedEdgeExists = ProBuilderEditor.instance.hovering.edges.Count > 0;
|
||||
|
||||
// if there is an edge already highlighted, we don't want to select a different game object
|
||||
if (highlightedEdgeExists)
|
||||
{
|
||||
ProBuilderMesh hoveredMeshFromHighlight = ProBuilderEditor.instance.hovering.mesh;
|
||||
if (hoveredMeshFromHighlight != null && ProBuilderEditor.instance.hovering.edges.Count > 0)
|
||||
{
|
||||
Edge highlightedEdge = ProBuilderEditor.instance.hovering.edges[0]; // Assuming single edge hover
|
||||
|
||||
// Calculate the screen distance for this specific highlighted edge.
|
||||
Vector3[] positions = hoveredMeshFromHighlight.positionsInternal;
|
||||
Vector3 worldPosA = hoveredMeshFromHighlight.transform.TransformPoint(positions[highlightedEdge.a]);
|
||||
Vector3 worldPosB = hoveredMeshFromHighlight.transform.TransformPoint(positions[highlightedEdge.b]);
|
||||
|
||||
if (ProcessEdgePoints(Camera.current, worldPosA, worldPosB, out Vector3 guiPointA, out Vector3 guiPointB))
|
||||
{
|
||||
float distToHighlightedEdge = HandleUtility.DistancePointLine(mousePosition, guiPointA, guiPointB);
|
||||
|
||||
// If the highlighted edge is within the maximum picking distance, select it and return early.
|
||||
if (distToHighlightedEdge <= ScenePickerPreferences.maxPointerDistance)
|
||||
{
|
||||
selection.gameObject = hoveredMeshFromHighlight.gameObject;
|
||||
selection.mesh = hoveredMeshFromHighlight;
|
||||
selection.SetSingleEdge(highlightedEdge);
|
||||
return distToHighlightedEdge;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float bestDistance = Mathf.Infinity;
|
||||
selection.gameObject = HandleUtility.PickGameObject(mousePosition, false);
|
||||
var hoveredMesh = selection.gameObject != null ? selection.gameObject.GetComponent<ProBuilderMesh>() : null;
|
||||
|
||||
bool hoveredIsInSelection = MeshSelection.topInternal.Contains(hoveredMesh);
|
||||
|
||||
if (hoveredMesh != null && (allowUnselected || hoveredIsInSelection))
|
||||
{
|
||||
var tup = GetNearestEdgeOnMesh(hoveredMesh, mousePosition);
|
||||
|
||||
if (tup.edge.IsValid())
|
||||
{
|
||||
selection.gameObject = hoveredMesh.gameObject;
|
||||
selection.mesh = hoveredMesh;
|
||||
selection.SetSingleEdge(tup.edge);
|
||||
bestDistance = tup.distance;
|
||||
|
||||
// If the nearest edge was acquired by a raycast on an already selected mesh,
|
||||
// return early to prioritize it.
|
||||
if (hoveredIsInSelection)
|
||||
return tup.distance;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var mesh in MeshSelection.topInternal)
|
||||
{
|
||||
var trs = mesh.transform;
|
||||
var positions = mesh.positionsInternal;
|
||||
s_EdgeBuffer.Clear();
|
||||
|
||||
// When the pointer is over another object, apply a modifier to the distance to prefer picking the
|
||||
// object hovered over the currently selected
|
||||
var distMultiplier = (hoveredMesh == mesh || hoveredMesh == null)
|
||||
? 1.0f
|
||||
: ScenePickerPreferences.offPointerMultiplier;
|
||||
|
||||
foreach (var face in mesh.facesInternal)
|
||||
{
|
||||
foreach (var edge in face.edges)
|
||||
{
|
||||
int x = edge.a;
|
||||
int y = edge.b;
|
||||
|
||||
Vector3 worldPosA = trs.TransformPoint(positions[x]);
|
||||
Vector3 worldPosB = trs.TransformPoint(positions[y]);
|
||||
|
||||
if (ProcessEdgePoints(Camera.current, worldPosA, worldPosB, out Vector3 guiPointA, out Vector3 guiPointB))
|
||||
{
|
||||
// Calculate distance from the mouse position to the line
|
||||
float d = HandleUtility.DistancePointLine(mousePosition, guiPointA, guiPointB);
|
||||
d *= distMultiplier;
|
||||
|
||||
// best distance isn't set to maxPointerDistance because we want to preserve an unselected
|
||||
// gameobject over a selected gameobject with an out of bounds edge.
|
||||
if (d > ScenePickerPreferences.maxPointerDistance)
|
||||
continue;
|
||||
|
||||
// account for stacked edges
|
||||
if (Mathf.Approximately(d, bestDistance))
|
||||
{
|
||||
s_EdgeBuffer.Add(new Edge(x, y));
|
||||
}
|
||||
else if (d < bestDistance)
|
||||
{
|
||||
s_EdgeBuffer.Clear();
|
||||
s_EdgeBuffer.Add(new Edge(x, y));
|
||||
|
||||
selection.gameObject = mesh.gameObject;
|
||||
selection.mesh = mesh;
|
||||
selection.SetSingleEdge(new Edge(x, y));
|
||||
bestDistance = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If more than 1 edge is closest, the closest is one of the vertex.
|
||||
// Get closest edge to the camera.
|
||||
if (s_EdgeBuffer.Count > 1)
|
||||
selection.SetSingleEdge(GetClosestEdgeToCamera(positions, s_EdgeBuffer));
|
||||
}
|
||||
|
||||
return selection.gameObject != null ? bestDistance : Mathf.Infinity;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Function to clip a point to the near plane.
|
||||
/// </summary>
|
||||
/// <param name="pointBehind"> the point behind the plane</param>
|
||||
/// <param name="pointInFront"> the point in front of te plane</param>
|
||||
/// <param name="nearPlaneZ"> the camera near plane z</param>
|
||||
/// <returns>The intersection point between the line and the near plane</returns>
|
||||
static Vector3 ClipPointToNearPlane(Vector3 pointBehind, Vector3 pointInFront, float nearPlaneZ)
|
||||
{
|
||||
float t = (nearPlaneZ - pointBehind.z) / (pointInFront.z - pointBehind.z);
|
||||
return pointBehind + t * (pointInFront - pointBehind);
|
||||
}
|
||||
|
||||
static Edge GetClosestEdgeToCamera(Vector3[] positions, IEnumerable<Edge> edges)
|
||||
{
|
||||
var camPos = SceneView.lastActiveSceneView.camera.transform.position;
|
||||
var closestDistToScreen = Mathf.Infinity;
|
||||
Edge closest = default(Edge);
|
||||
|
||||
foreach (var edge in edges)
|
||||
{
|
||||
var a = positions[edge.a];
|
||||
var b = positions[edge.b];
|
||||
var dir = (b - a).normalized * 0.01f;
|
||||
|
||||
//Use a point that is close to the vertex on the edge but not on it,
|
||||
//otherwise we will have the same issue with every edge having the same distance to screen
|
||||
float dToScreen = Mathf.Min(
|
||||
Vector3.Distance(camPos, a + dir),
|
||||
Vector3.Distance(camPos, b - dir));
|
||||
|
||||
if (dToScreen < closestDistToScreen)
|
||||
{
|
||||
closestDistToScreen = dToScreen;
|
||||
closest = edge;
|
||||
}
|
||||
}
|
||||
|
||||
return closest;
|
||||
}
|
||||
|
||||
struct EdgeAndDistance
|
||||
{
|
||||
public Edge edge;
|
||||
public float distance;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the nearest edge to a screen position.
|
||||
/// </summary>
|
||||
static EdgeAndDistance GetNearestEdgeOnMesh(ProBuilderMesh mesh, Vector3 mousePosition)
|
||||
{
|
||||
Ray ray = UHandleUtility.GUIPointToWorldRay(mousePosition);
|
||||
|
||||
var res = new EdgeAndDistance()
|
||||
{
|
||||
edge = Edge.Empty,
|
||||
distance = Mathf.Infinity
|
||||
};
|
||||
|
||||
SimpleTuple<Face, Vector3> s_DualCullModeRaycastBackFace = new SimpleTuple<Face, Vector3>();
|
||||
SimpleTuple<Face, Vector3> s_DualCullModeRaycastFrontFace = new SimpleTuple<Face, Vector3>();
|
||||
|
||||
// get the nearest hit face and point for both cull mode front and back, then prefer the result that is nearest the camera.
|
||||
if (PHandleUtility.FaceRaycastBothCullModes(ray, mesh, ref s_DualCullModeRaycastBackFace, ref s_DualCullModeRaycastFrontFace))
|
||||
{
|
||||
Vector3[] v = mesh.positionsInternal;
|
||||
|
||||
if (s_DualCullModeRaycastBackFace.item1 != null)
|
||||
{
|
||||
foreach (var edge in s_DualCullModeRaycastBackFace.item1.edgesInternal)
|
||||
{
|
||||
float d = UHandleUtility.DistancePointLine(s_DualCullModeRaycastBackFace.item2, v[edge.a], v[edge.b]);
|
||||
|
||||
if (d < res.distance)
|
||||
{
|
||||
res.edge = edge;
|
||||
res.distance = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (s_DualCullModeRaycastFrontFace.item1 != null)
|
||||
{
|
||||
var a = mesh.transform.TransformPoint(s_DualCullModeRaycastBackFace.item2);
|
||||
var b = mesh.transform.TransformPoint(s_DualCullModeRaycastFrontFace.item2);
|
||||
var c = SceneView.lastActiveSceneView.camera.transform.position;
|
||||
|
||||
if (Vector3.Distance(c, b) < Vector3.Distance(c, a))
|
||||
{
|
||||
foreach (var edge in s_DualCullModeRaycastFrontFace.item1.edgesInternal)
|
||||
{
|
||||
float d = UHandleUtility.DistancePointLine(s_DualCullModeRaycastFrontFace.item2, v[edge.a], v[edge.b]);
|
||||
|
||||
if (d < res.distance)
|
||||
{
|
||||
res.edge = edge;
|
||||
res.distance = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (res.edge.IsValid())
|
||||
{
|
||||
Vector3 worldPosA = mesh.transform.TransformPoint(v[res.edge.a]);
|
||||
Vector3 worldPosB = mesh.transform.TransformPoint(v[res.edge.b]);
|
||||
|
||||
if (ProcessEdgePoints(Camera.current, worldPosA, worldPosB, out Vector3 guiPointA, out Vector3 guiPointB))
|
||||
{
|
||||
// Calculate distance from the mouse position to the line
|
||||
res.distance = HandleUtility.DistancePointLine(mousePosition, guiPointA, guiPointB);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If both points are behind the camera, skip this edge
|
||||
res.distance = Mathf.Infinity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static bool ProcessEdgePoints(Camera camera, Vector3 worldPosA, Vector3 worldPosB, out Vector3 guiPointA, out Vector3 guiPointB)
|
||||
{
|
||||
guiPointA = Vector3.zero;
|
||||
guiPointB = Vector3.zero;
|
||||
|
||||
// Check if the points are in front of the camera
|
||||
bool pointAInFront = Vector3.Dot(camera.transform.forward, worldPosA - camera.transform.position) > 0;
|
||||
bool pointBInFront = Vector3.Dot(camera.transform.forward, worldPosB - camera.transform.position) > 0;
|
||||
|
||||
// If both points are behind the camera, skip this edge
|
||||
if (!pointAInFront && !pointBInFront)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if either point is behind the camera
|
||||
if (!pointAInFront || !pointBInFront)
|
||||
{
|
||||
// Clip points against the near plane
|
||||
Vector3 cameraSpacePointA = camera.transform.InverseTransformPoint(worldPosA);
|
||||
Vector3 cameraSpacePointB = camera.transform.InverseTransformPoint(worldPosB);
|
||||
|
||||
float nearPlaneZ = camera.nearClipPlane;
|
||||
|
||||
if (cameraSpacePointA.z < nearPlaneZ)
|
||||
{
|
||||
cameraSpacePointA = ClipPointToNearPlane(cameraSpacePointA, cameraSpacePointB, nearPlaneZ);
|
||||
}
|
||||
|
||||
if (cameraSpacePointB.z < nearPlaneZ)
|
||||
{
|
||||
cameraSpacePointB = ClipPointToNearPlane(cameraSpacePointB, cameraSpacePointA, nearPlaneZ);
|
||||
}
|
||||
|
||||
// Transform clipped points back to screen space
|
||||
guiPointA = HandleUtility.WorldToGUIPoint(camera.transform.TransformPoint(cameraSpacePointA));
|
||||
guiPointB = HandleUtility.WorldToGUIPoint(camera.transform.TransformPoint(cameraSpacePointB));
|
||||
}
|
||||
else // Both points are in front of the camera
|
||||
{
|
||||
guiPointA = HandleUtility.WorldToGUIPoint(worldPosA);
|
||||
guiPointB = HandleUtility.WorldToGUIPoint(worldPosB);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6420831531bc2054281143153f7d6659
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.SettingsManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder.Shapes;
|
||||
using Math = UnityEngine.ProBuilder.Math;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
static class EditorShapeUtility
|
||||
{
|
||||
[UserSetting]
|
||||
public static Pref<bool> s_ResetUserPrefs = new Pref<bool>("ShapeComponent.ResetSettings", true);
|
||||
static Dictionary<string, Shape> s_Prefs = new Dictionary<string, Shape>();
|
||||
static readonly string k_Tooltip = L10n.Tr("Click and drag to place and scale the shape, or SHIFT+click once to duplicate last size settings.");
|
||||
|
||||
static Dictionary<string, Shape> prefs
|
||||
{
|
||||
get
|
||||
{
|
||||
if(s_ResetUserPrefs.value)
|
||||
ResetPrefs();
|
||||
|
||||
return s_Prefs;
|
||||
}
|
||||
}
|
||||
|
||||
static Type[] s_AvailableShapeTypes = null;
|
||||
|
||||
public static Type[] availableShapeTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if(s_AvailableShapeTypes == null)
|
||||
s_AvailableShapeTypes = TypeCache.GetTypesWithAttribute<ShapeAttribute>().Where(t => t.BaseType == typeof(Shape)).ToArray();
|
||||
return s_AvailableShapeTypes;
|
||||
}
|
||||
}
|
||||
|
||||
static string[] s_ShapeTypes;
|
||||
|
||||
public static string[] shapeTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
if( s_ShapeTypes == null)
|
||||
s_ShapeTypes = availableShapeTypes.Select(x => ((ShapeAttribute)System.Attribute.GetCustomAttribute(x, typeof(ShapeAttribute))).name)
|
||||
.ToArray();
|
||||
return s_ShapeTypes;
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetName<T>(T shape) where T : Shape
|
||||
{
|
||||
var type = shape == null ? typeof(T) : shape.GetType();
|
||||
if (Attribute.GetCustomAttribute(type, typeof(ShapeAttribute)) is ShapeAttribute attrib)
|
||||
return attrib.name;
|
||||
var str = type.ToString();
|
||||
return str.Substring(str.LastIndexOf('.') + 1);
|
||||
}
|
||||
|
||||
static int s_MaxContentPerGroup = 6;
|
||||
|
||||
public static int MaxContentPerGroup
|
||||
{
|
||||
get => s_MaxContentPerGroup;
|
||||
}
|
||||
|
||||
static List<GUIContent[]> s_ShapeTypesGUILists;
|
||||
|
||||
public static List<GUIContent[]> shapeTypesGUI
|
||||
{
|
||||
get
|
||||
{
|
||||
if(s_ShapeTypesGUILists == null)
|
||||
{
|
||||
s_ShapeTypesGUILists = new List<GUIContent[]>();
|
||||
string[] shapeTypeNames = availableShapeTypes.Select(x => ((ShapeAttribute) Attribute.GetCustomAttribute(x, typeof(ShapeAttribute))).name).ToArray();
|
||||
GUIContent[] shapeTypesGUI = null;
|
||||
|
||||
int i;
|
||||
for(i = 0; i < shapeTypeNames.Length; i++)
|
||||
{
|
||||
if(i % s_MaxContentPerGroup == 0)
|
||||
{
|
||||
if(shapeTypesGUI != null) s_ShapeTypesGUILists.Add(shapeTypesGUI);
|
||||
int maxLength = Mathf.Min(s_MaxContentPerGroup, ( shapeTypeNames.Length - i ));
|
||||
shapeTypesGUI = new GUIContent[maxLength];
|
||||
}
|
||||
var name = shapeTypeNames[i];
|
||||
var texture = IconUtility.GetIcon("Tools/ShapeTool/" + name);
|
||||
if(texture != null)
|
||||
shapeTypesGUI[i % s_MaxContentPerGroup] = new GUIContent(texture, k_Tooltip);
|
||||
else
|
||||
shapeTypesGUI[i % s_MaxContentPerGroup] = new GUIContent(name, k_Tooltip);
|
||||
}
|
||||
|
||||
if(shapeTypesGUI != null) s_ShapeTypesGUILists.Add(shapeTypesGUI);
|
||||
|
||||
}
|
||||
return s_ShapeTypesGUILists;
|
||||
}
|
||||
}
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_AvailableShapeTypes = null;
|
||||
s_ShapeTypes = null;
|
||||
s_ShapeTypesGUILists = null;
|
||||
}
|
||||
|
||||
static EditorShapeUtility()
|
||||
{
|
||||
ResetPrefs();
|
||||
}
|
||||
|
||||
static void ResetPrefs()
|
||||
{
|
||||
var types = TypeCache.GetTypesDerivedFrom<Shape>();
|
||||
|
||||
foreach(var type in types)
|
||||
{
|
||||
if(typeof(Shape).IsAssignableFrom(type) && !type.IsAbstract)
|
||||
{
|
||||
var name = "ShapeBuilder." + type.Name;
|
||||
var pref = ProBuilderSettings.Get(name, SettingsScope.Project, (Shape) Activator.CreateInstance(type));
|
||||
if(pref == null)
|
||||
pref = (Shape) Activator.CreateInstance(type);
|
||||
|
||||
s_Prefs[name] = pref;
|
||||
}
|
||||
}
|
||||
|
||||
s_ResetUserPrefs.value = false;
|
||||
}
|
||||
|
||||
public static void SaveParams<T>(T shape) where T : Shape
|
||||
{
|
||||
var name = "ShapeBuilder." + shape.GetType().Name;
|
||||
if (prefs.TryGetValue(name, out var data))
|
||||
{
|
||||
data.CopyShape(shape);
|
||||
prefs[name] = data;
|
||||
ProBuilderSettings.Set(name, data);
|
||||
}
|
||||
}
|
||||
|
||||
public static void CopyLastParams(Shape shape, Type type)
|
||||
{
|
||||
if (!typeof(Shape).IsAssignableFrom(type))
|
||||
{
|
||||
throw new ArgumentException(nameof(type));
|
||||
}
|
||||
|
||||
if(shape == null)
|
||||
{
|
||||
try
|
||||
{
|
||||
shape = Activator.CreateInstance(type) as Shape;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.LogError(
|
||||
$"Cannot create shape of type {type.ToString()} because it doesn't have a default constructor.");
|
||||
}
|
||||
}
|
||||
|
||||
var name = "ShapeBuilder." + type.Name;
|
||||
if (prefs.TryGetValue(name, out var data))
|
||||
{
|
||||
if (data != null)
|
||||
shape.CopyShape(data);
|
||||
}
|
||||
}
|
||||
|
||||
public static Shape CreateShape(Type type, bool copyLastParams = true)
|
||||
{
|
||||
Shape shape = null;
|
||||
try
|
||||
{
|
||||
shape = Activator.CreateInstance(type) as Shape;
|
||||
}
|
||||
catch
|
||||
{
|
||||
Debug.LogError($"Cannot create shape of type { type.ToString() } because it doesn't have a default constructor.");
|
||||
}
|
||||
|
||||
if(shape == null)
|
||||
return null;
|
||||
|
||||
if (copyLastParams)
|
||||
CopyLastParams(shape, type);
|
||||
|
||||
return shape;
|
||||
}
|
||||
|
||||
public sealed class FaceData
|
||||
{
|
||||
public Vector3 CenterPosition;
|
||||
public Vector3 Normal;
|
||||
public Vector3[] Points;
|
||||
public bool IsValid = true;
|
||||
|
||||
public bool IsVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
Vector3 worldDir = Handles.matrix.MultiplyVector(Normal).normalized;
|
||||
|
||||
Vector3 cameraDir;
|
||||
if (Camera.current.orthographic)
|
||||
cameraDir = Camera.current.transform.forward;
|
||||
else
|
||||
cameraDir = (Handles.matrix.MultiplyPoint(CenterPosition) - Camera.current.transform.position).normalized;
|
||||
|
||||
return Vector3.Dot(cameraDir, worldDir) < 0;
|
||||
}
|
||||
}
|
||||
|
||||
public FaceData()
|
||||
{
|
||||
Points = new Vector3[4];
|
||||
}
|
||||
|
||||
public void SetData(Vector3 centerPosition, Vector3 normal)
|
||||
{
|
||||
CenterPosition = centerPosition;
|
||||
Normal = normal;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateFaces(Bounds bounds, Vector3 scale, FaceData[] faces)
|
||||
{
|
||||
if(faces.Length != 6)
|
||||
faces = new FaceData[6];
|
||||
|
||||
Vector3 extents = bounds.extents;
|
||||
|
||||
Vector3 pointX0Y0Z0 = Vector3.Scale(new Vector3(-extents.x, -extents.y, -extents.z), scale);
|
||||
Vector3 pointX1Y0Z0 = Vector3.Scale(new Vector3(extents.x, -extents.y, -extents.z), scale);
|
||||
Vector3 pointX0Y1Z0 = Vector3.Scale(new Vector3(-extents.x, extents.y, -extents.z), scale);
|
||||
Vector3 pointX0Y0Z1 = Vector3.Scale(new Vector3(-extents.x, -extents.y, extents.z), scale);
|
||||
Vector3 pointX1Y1Z0 = Vector3.Scale(new Vector3(extents.x, extents.y, -extents.z), scale);
|
||||
Vector3 pointX1Y0Z1 = Vector3.Scale(new Vector3(extents.x, -extents.y, extents.z), scale);
|
||||
Vector3 pointX0Y1Z1 = Vector3.Scale(new Vector3(-extents.x, extents.y, extents.z), scale);
|
||||
Vector3 pointX1Y1Z1 = Vector3.Scale(new Vector3(extents.x, extents.y, extents.z), scale);
|
||||
|
||||
var signs = Math.Sign(bounds.size);
|
||||
var pos = Vector3.zero;
|
||||
|
||||
// -X
|
||||
pos = -new Vector3(extents.x * scale.x, 0, 0);
|
||||
faces[0].SetData(pos, -( scale.x * signs.x * Vector3.right ).normalized);
|
||||
faces[0].Points[0] = pointX0Y1Z1;
|
||||
faces[0].Points[1] = pointX0Y0Z1;
|
||||
faces[0].Points[2] = pointX0Y0Z0;
|
||||
faces[0].Points[3] = pointX0Y1Z0;
|
||||
faces[0].IsValid = Mathf.Abs(extents.x) > Mathf.Epsilon;
|
||||
|
||||
// +X
|
||||
pos = new Vector3(extents.x * scale.x, 0, 0);
|
||||
faces[1].SetData(pos, ( scale.x * signs.x * Vector3.right ).normalized);
|
||||
faces[1].Points[0] = pointX1Y1Z1;
|
||||
faces[1].Points[1] = pointX1Y0Z1;
|
||||
faces[1].Points[2] = pointX1Y0Z0;
|
||||
faces[1].Points[3] = pointX1Y1Z0;
|
||||
faces[1].IsValid = Mathf.Abs(extents.x) > Mathf.Epsilon;
|
||||
|
||||
// -Y
|
||||
pos = -new Vector3(0, extents.y * scale.y, 0);
|
||||
faces[2].SetData(pos, -( scale.y * signs.y * Vector3.up ).normalized);
|
||||
faces[2].Points[0] = pointX1Y0Z1;
|
||||
faces[2].Points[1] = pointX0Y0Z1;
|
||||
faces[2].Points[2] = pointX0Y0Z0;
|
||||
faces[2].Points[3] = pointX1Y0Z0;
|
||||
faces[2].IsValid = Mathf.Abs(extents.y) > Mathf.Epsilon;
|
||||
|
||||
// +Y
|
||||
pos = new Vector3(0, extents.y * scale.y, 0);
|
||||
faces[3].SetData(pos, ( scale.y * signs.y * Vector3.up ).normalized);
|
||||
faces[3].Points[0] = pointX1Y1Z1;
|
||||
faces[3].Points[1] = pointX0Y1Z1;
|
||||
faces[3].Points[2] = pointX0Y1Z0;
|
||||
faces[3].Points[3] = pointX1Y1Z0;
|
||||
faces[3].IsValid = Mathf.Abs(extents.y) > Mathf.Epsilon;
|
||||
|
||||
// -Z
|
||||
pos = -new Vector3(0, 0, extents.z * scale.z);
|
||||
faces[4].SetData(pos, -(scale.z * signs.z * Vector3.forward).normalized);
|
||||
faces[4].Points[0] = pointX1Y1Z0;
|
||||
faces[4].Points[1] = pointX1Y0Z0;
|
||||
faces[4].Points[2] = pointX0Y0Z0;
|
||||
faces[4].Points[3] = pointX0Y1Z0;
|
||||
faces[4].IsValid = Mathf.Abs(extents.z) > Mathf.Epsilon;
|
||||
|
||||
// +Z
|
||||
pos = new Vector3(0, 0, extents.z * scale.z);
|
||||
faces[5].SetData(pos, (scale.z * signs.z * Vector3.forward).normalized);
|
||||
faces[5].Points[0] = pointX1Y1Z1;
|
||||
faces[5].Points[1] = pointX1Y0Z1;
|
||||
faces[5].Points[2] = pointX0Y0Z1;
|
||||
faces[5].Points[3] = pointX0Y1Z1;
|
||||
faces[5].IsValid = Mathf.Abs(extents.z) > Mathf.Epsilon;
|
||||
}
|
||||
|
||||
internal static bool PointerIsInFace(FaceData face)
|
||||
{
|
||||
if(!face.IsVisible)
|
||||
return false;
|
||||
|
||||
Vector2[] face2D = new Vector2[4];
|
||||
for(int i = 0; i < face.Points.Length; i++)
|
||||
{
|
||||
face2D[i] = HandleUtility.WorldToGUIPoint(face.Points[i]);
|
||||
}
|
||||
return PointInQuad2D(Event.current.mousePosition, face2D);
|
||||
}
|
||||
|
||||
static bool PointInQuad2D(Vector2 point, Vector2[] quadPoints)
|
||||
{
|
||||
bool inQuad = true;
|
||||
|
||||
Vector2[] points = { point, quadPoints[2], quadPoints[3] };
|
||||
inQuad &= SameSide(quadPoints[0], quadPoints[1], points);
|
||||
points[1] = quadPoints[0]; // { point, quadPoints[0], quadPoints[3]};
|
||||
inQuad &= SameSide(quadPoints[1], quadPoints[2], points);
|
||||
points[2] = quadPoints[1]; // { point, quadPoints[0], quadPoints[1]};
|
||||
inQuad &= SameSide(quadPoints[2], quadPoints[3], points);
|
||||
points[1] = quadPoints[2]; // { point, quadPoints[2], quadPoints[1]};
|
||||
inQuad &= SameSide(quadPoints[3], quadPoints[0], points);
|
||||
|
||||
return inQuad;
|
||||
}
|
||||
|
||||
static bool SameSide(Vector2 pStart, Vector2 pEnd, Vector2[] points)
|
||||
{
|
||||
if(points.Length < 2)
|
||||
return true;
|
||||
|
||||
var cpRef = Vector3.Cross(pEnd - pStart, points[0] - pStart);
|
||||
for(int i = 1; i < points.Length; i++)
|
||||
{
|
||||
var cp = Vector3.Cross(pEnd - pStart, points[i] - pStart);
|
||||
if(Vector3.Dot(cpRef, cp) < 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aaf91fe98a345dc46b1839d422f87be2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,135 @@
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEditor.SettingsManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
enum SnapAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// When an <see cref="EditorTool"/> is modifying vertices, snap vertex positions only to the axis that is currently moving.
|
||||
/// </summary>
|
||||
ActiveAxis,
|
||||
/// <summary>
|
||||
/// When an <see cref="EditorTool"/> is modifying vertices, snap vertex positions in all axis directions.
|
||||
/// </summary>
|
||||
AllAxes
|
||||
}
|
||||
|
||||
enum SnapMode
|
||||
{
|
||||
None,
|
||||
Relative,
|
||||
World
|
||||
}
|
||||
|
||||
// Snapping for ProBuilder tools. If ProGrids is active, ProGrids settings will take priority. Otherwise the Unity
|
||||
// EditorSnap state is used.
|
||||
static class EditorSnapping
|
||||
{
|
||||
[UserSetting("Snap Settings", "Snap as Group", "When enabled, selected mesh elements will keep their relative offsets when snapping to the grid. When disabled, every element in the selection is snapped to grid independently.")]
|
||||
static Pref<bool> s_SnapAsGroup = new Pref<bool>("ProBuilder.SnapSettings.s_SnapAsGroup", true, SettingsScope.User);
|
||||
|
||||
|
||||
[UserSetting("Snap Settings", "Snap Axis", "When an Editor Tool is modifying vertices, this setting determines in which directions the affected vertices will snap.")]
|
||||
static Pref<SnapAxis> s_SnapAxis = new Pref<SnapAxis>("ProBuilder.SnapSettings.s_SnapAxis", SnapAxis.ActiveAxis, SettingsScope.User);
|
||||
|
||||
public static SnapMode snapMode
|
||||
{
|
||||
get
|
||||
{
|
||||
if (EditorSnapSettings.incrementalSnapActive)
|
||||
return SnapMode.Relative;
|
||||
if (ProGridsInterface.SnapEnabled() || EditorSnapSettings.gridSnapActive && Tools.pivotRotation == PivotRotation.Global)
|
||||
return SnapMode.World;
|
||||
return SnapMode.None;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool snapAsGroup
|
||||
{
|
||||
get { return ProGridsInterface.IsActive() ? ProGridsInterface.GetSnapAsGroup() : s_SnapAsGroup.value; }
|
||||
}
|
||||
|
||||
internal static SnapAxis snapMethod
|
||||
{
|
||||
get { return ProGridsInterface.IsActive() ? ProGridsInterface.GetSnapMethod() : s_SnapAxis.value; }
|
||||
}
|
||||
|
||||
internal static Vector3 activeMoveSnapValue
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (snapMode)
|
||||
{
|
||||
case SnapMode.Relative:
|
||||
return incrementalSnapMoveValue;
|
||||
case SnapMode.World:
|
||||
return worldSnapMoveValue;
|
||||
default:
|
||||
return Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static Vector3 activeScaleSnapValue
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (snapMode)
|
||||
{
|
||||
case SnapMode.Relative:
|
||||
case SnapMode.World:
|
||||
return Vector3.one * incrementalSnapScaleValue;
|
||||
default:
|
||||
return Vector3.zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static float MoveSnap(float value)
|
||||
{
|
||||
return ProBuilderSnapping.Snap(value, activeMoveSnapValue.x);
|
||||
}
|
||||
|
||||
public static Vector3 MoveSnap(Vector3 value)
|
||||
{
|
||||
return ProBuilderSnapping.Snap(value, activeMoveSnapValue);
|
||||
}
|
||||
|
||||
public static float RotateSnap(float value)
|
||||
{
|
||||
return ProBuilderSnapping.Snap(value, activeRotateSnapValue);
|
||||
}
|
||||
|
||||
public static Vector3 ScaleSnap(Vector3 value)
|
||||
{
|
||||
return ProBuilderSnapping.Snap(value, Vector3.one * activeRotateSnapValue);
|
||||
}
|
||||
|
||||
internal static float activeRotateSnapValue
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (snapMode)
|
||||
{
|
||||
case SnapMode.Relative:
|
||||
case SnapMode.World:
|
||||
return incrementalSnapRotateValue;
|
||||
default:
|
||||
return 0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector3 incrementalSnapMoveValue => EditorSnapSettings.move;
|
||||
|
||||
public static Vector3 worldSnapMoveValue
|
||||
=> ProGridsInterface.IsActive() ? ProGridsInterface.SnapValue() * Vector3.one : GridSettings.size;
|
||||
|
||||
public static float incrementalSnapRotateValue => EditorSnapSettings.rotate;
|
||||
|
||||
public static float incrementalSnapScaleValue => EditorSnapSettings.scale;
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 428fa7a76260b497d8306bc5162aa463
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.ProBuilder.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Collection of commonly used styles in the editor.
|
||||
/// </summary>
|
||||
static class EditorStyles
|
||||
{
|
||||
const string k_FontAsap = "About/Font/Asap-Regular.otf";
|
||||
|
||||
const string k_ButtonBackgroundNormal = "Toolbar/Background/RoundedRect_Normal";
|
||||
const string k_ButtonBackgroundHover = "Toolbar/Background/RoundedRect_Hover";
|
||||
const string k_ButtonBackgroundPressed = "Toolbar/Background/RoundedRect_Pressed";
|
||||
const string k_SettingsBackgroundNormal = "Toolbar/RoundedBorder";
|
||||
const string k_SceneTextBoxBackgroundNormal = "Scene/TextBackground";
|
||||
|
||||
static readonly Color k_TextColorWhiteNormal = new Color(0.7f, 0.7f, 0.7f, 1f);
|
||||
static readonly Color k_TextColorWhiteHover = new Color(0.7f, 0.7f, 0.7f, 1f);
|
||||
static readonly Color k_TextColorWhiteActive = new Color(0.5f, 0.5f, 0.5f, 1f);
|
||||
|
||||
static bool s_Initialized;
|
||||
static GUIStyle s_ButtonStyle;
|
||||
static GUIStyle s_ToolbarHelpIcon;
|
||||
static GUIStyle s_SettingsGroup;
|
||||
static GUIStyle s_RowStyle;
|
||||
static GUIStyle s_HeaderLabel;
|
||||
static GUIStyle s_SceneTextBox;
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_Initialized = false;
|
||||
}
|
||||
|
||||
public static GUIStyle buttonStyle { get { Init(); return s_ButtonStyle; } }
|
||||
public static GUIStyle toolbarHelpIcon { get { Init(); return s_ToolbarHelpIcon; } }
|
||||
public static GUIStyle settingsGroup { get { Init(); return s_SettingsGroup; } }
|
||||
public static GUIStyle rowStyle { get { Init(); return s_RowStyle; } }
|
||||
public static GUIStyle headerLabel { get { Init(); return s_HeaderLabel; } }
|
||||
public static GUIStyle sceneTextBox { get { Init(); return s_SceneTextBox; } }
|
||||
|
||||
static void Init()
|
||||
{
|
||||
if (s_Initialized)
|
||||
return;
|
||||
|
||||
s_Initialized = true;
|
||||
|
||||
s_ButtonStyle = new GUIStyle()
|
||||
{
|
||||
normal = new GUIStyleState()
|
||||
{
|
||||
background = IconUtility.GetIcon(k_ButtonBackgroundNormal),
|
||||
textColor = UnityEditor.EditorGUIUtility.isProSkin ? k_TextColorWhiteNormal : Color.black
|
||||
},
|
||||
hover = new GUIStyleState()
|
||||
{
|
||||
background = IconUtility.GetIcon(k_ButtonBackgroundHover),
|
||||
textColor = UnityEditor.EditorGUIUtility.isProSkin ? k_TextColorWhiteHover : Color.black,
|
||||
},
|
||||
active = new GUIStyleState()
|
||||
{
|
||||
background = IconUtility.GetIcon(k_ButtonBackgroundPressed),
|
||||
textColor = UnityEditor.EditorGUIUtility.isProSkin ? k_TextColorWhiteActive : Color.black,
|
||||
},
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
border = new RectOffset(3, 3, 3, 3),
|
||||
stretchWidth = true,
|
||||
stretchHeight = false,
|
||||
margin = new RectOffset(4, 4, 4, 4),
|
||||
padding = new RectOffset(4, 4, 4, 4)
|
||||
};
|
||||
|
||||
s_ToolbarHelpIcon = new GUIStyle()
|
||||
{
|
||||
margin = new RectOffset(0, 0, 0, 0),
|
||||
padding = new RectOffset(0, 0, 0, 0),
|
||||
alignment = TextAnchor.MiddleCenter,
|
||||
fixedWidth = 18,
|
||||
fixedHeight = 18
|
||||
};
|
||||
|
||||
s_SettingsGroup = new GUIStyle()
|
||||
{
|
||||
normal = new GUIStyleState()
|
||||
{
|
||||
background = IconUtility.GetIcon(k_SettingsBackgroundNormal)
|
||||
},
|
||||
hover = new GUIStyleState()
|
||||
{
|
||||
background = IconUtility.GetIcon(k_SettingsBackgroundNormal)
|
||||
},
|
||||
active = new GUIStyleState()
|
||||
{
|
||||
background = IconUtility.GetIcon(k_SettingsBackgroundNormal)
|
||||
},
|
||||
border = new RectOffset(3, 3, 3, 3),
|
||||
stretchWidth = true,
|
||||
stretchHeight = false,
|
||||
margin = new RectOffset(4, 4, 4, 4),
|
||||
padding = new RectOffset(4, 4, 4, 6)
|
||||
};
|
||||
|
||||
s_RowStyle = new GUIStyle()
|
||||
{
|
||||
normal = new GUIStyleState() { background = UnityEditor.EditorGUIUtility.whiteTexture },
|
||||
stretchWidth = true,
|
||||
stretchHeight = false,
|
||||
margin = new RectOffset(4, 4, 4, 4),
|
||||
padding = new RectOffset(4, 4, 4, 4)
|
||||
};
|
||||
|
||||
s_HeaderLabel = new GUIStyle(UnityEditor.EditorStyles.boldLabel)
|
||||
{
|
||||
alignment = TextAnchor.LowerLeft,
|
||||
fontSize = 18,
|
||||
stretchWidth = true,
|
||||
stretchHeight = false
|
||||
};
|
||||
|
||||
Font asap = FileUtility.LoadInternalAsset<Font>(k_FontAsap);
|
||||
if (asap != null)
|
||||
s_HeaderLabel.font = asap;
|
||||
|
||||
s_SceneTextBox = new GUIStyle(GUI.skin.box)
|
||||
{
|
||||
wordWrap = false,
|
||||
richText = true,
|
||||
stretchWidth = false,
|
||||
stretchHeight = false,
|
||||
border = new RectOffset(2, 2, 2, 2),
|
||||
padding = new RectOffset(4, 4, 4, 4),
|
||||
alignment = TextAnchor.UpperLeft,
|
||||
normal = new GUIStyleState()
|
||||
{
|
||||
textColor = k_TextColorWhiteNormal,
|
||||
background = IconUtility.GetIcon(k_SceneTextBoxBackgroundNormal)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a08405bf933b15f4882645f9cb359193
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+176
@@ -0,0 +1,176 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.ProBuilder;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
#if UNITY_6000_5_OR_NEWER
|
||||
using UnityEngine.Assemblies;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for loading menu actions in to the pb_Toolbar.
|
||||
/// </summary>
|
||||
static class EditorToolbarLoader
|
||||
{
|
||||
static List<MenuAction> s_LoadedMenuActions;
|
||||
|
||||
public static T GetInstance<T>() where T : MenuAction, new()
|
||||
{
|
||||
T instance = (T)GetActions().FirstOrDefault(x => x is T);
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
instance = new T();
|
||||
if (s_LoadedMenuActions != null)
|
||||
s_LoadedMenuActions.Add(instance);
|
||||
else
|
||||
s_LoadedMenuActions = new List<MenuAction>() { instance };
|
||||
}
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
internal static List<MenuAction> GetActions(bool forceReload = false)
|
||||
{
|
||||
if (s_LoadedMenuActions != null && !forceReload)
|
||||
return s_LoadedMenuActions;
|
||||
|
||||
s_LoadedMenuActions = new List<MenuAction>()
|
||||
{
|
||||
// tools
|
||||
new Actions.NewBezierShape(),
|
||||
new Actions.OpenMaterialEditor(),
|
||||
new Actions.OpenUVEditor(),
|
||||
new Actions.OpenVertexColorEditor(),
|
||||
new Actions.OpenSmoothingEditor(),
|
||||
|
||||
new Actions.ToggleSelectBackFaces(),
|
||||
new Actions.ToggleHandleOrientation(),
|
||||
new Actions.ToggleDragRectMode(),
|
||||
new Actions.ToggleXRay(),
|
||||
|
||||
// selection
|
||||
new Actions.GrowSelection(),
|
||||
new Actions.ShrinkSelection(),
|
||||
new Actions.SelectEdgeLoop(),
|
||||
new Actions.SelectEdgeRing(),
|
||||
new Actions.SelectFaceLoop(),
|
||||
new Actions.SelectFaceRing(),
|
||||
new Actions.SelectHole(),
|
||||
new Actions.SelectVertexColor(),
|
||||
new Actions.SelectMaterial(),
|
||||
new Actions.SelectSmoothingGroup(),
|
||||
|
||||
// object
|
||||
new Actions.MergeObjects(),
|
||||
new Actions.MirrorObjects(),
|
||||
new Actions.FlipObjectNormals(),
|
||||
new Actions.SubdivideObject(),
|
||||
new Actions.FreezeTransform(),
|
||||
new Actions.CenterPivot(),
|
||||
new Actions.ConformObjectNormals(),
|
||||
new Actions.TriangulateObject(),
|
||||
new Actions.GenerateUV2(),
|
||||
new Actions.ProBuilderize(),
|
||||
new Actions.Export(),
|
||||
new Actions.ExportObj(),
|
||||
new Actions.ExportAsset(),
|
||||
new Actions.ExportPly(),
|
||||
new Actions.ExportStlAscii(),
|
||||
new Actions.ExportStlBinary(),
|
||||
|
||||
// All
|
||||
new Actions.SetPivotToSelection(),
|
||||
new Actions.OffsetElements(),
|
||||
new Actions.CutToolAction(),
|
||||
|
||||
// Faces (All)
|
||||
new Actions.DeleteFaces(),
|
||||
new Actions.DetachFaces(),
|
||||
new Actions.DuplicateFaces(),
|
||||
new Actions.ExtrudeFaces(),
|
||||
|
||||
// Face
|
||||
new Actions.ConformFaceNormals(),
|
||||
new Actions.FlipFaceEdge(),
|
||||
new Actions.FlipFaceNormals(),
|
||||
new Actions.MergeFaces(),
|
||||
new Actions.SubdivideFaces(),
|
||||
new Actions.TriangulateFaces(),
|
||||
|
||||
// Edge
|
||||
new Actions.BridgeEdges(),
|
||||
new Actions.BevelEdges(),
|
||||
new Actions.ConnectEdges(),
|
||||
new Actions.ExtrudeEdges(),
|
||||
new Actions.InsertEdgeLoop(),
|
||||
new Actions.SubdivideEdges(),
|
||||
|
||||
// Vertex
|
||||
new Actions.CollapseVertices(),
|
||||
new Actions.WeldVertices(),
|
||||
new Actions.ConnectVertices(),
|
||||
new Actions.FillHole(),
|
||||
new Actions.SplitVertices(),
|
||||
|
||||
// Entity
|
||||
#if ENABLE_ENTITY_TYPES
|
||||
new Actions.SetEntityType_Detail(),
|
||||
new Actions.SetEntityType_Mover(),
|
||||
new Actions.SetEntityType_Collider(),
|
||||
new Actions.SetEntityType_Trigger(),
|
||||
#endif
|
||||
new Actions.SetTrigger(),
|
||||
new Actions.SetCollider(),
|
||||
};
|
||||
|
||||
SearchForMenuAttributes(s_LoadedMenuActions);
|
||||
|
||||
s_LoadedMenuActions.Sort(MenuAction.CompareActionsByGroupAndPriority);
|
||||
|
||||
return s_LoadedMenuActions;
|
||||
}
|
||||
|
||||
static void SearchForMenuAttributes(List<MenuAction> list)
|
||||
{
|
||||
#if UNITY_6000_5_OR_NEWER
|
||||
foreach (var assembly in CurrentAssemblies.GetLoadedAssemblies())
|
||||
#else
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
#endif
|
||||
{
|
||||
try
|
||||
{
|
||||
var menuActionTypes = assembly.GetTypes().Where(x =>
|
||||
{
|
||||
if (!typeof(MenuAction).IsAssignableFrom(x)
|
||||
|| x.IsAbstract
|
||||
|| !Attribute.IsDefined(x, typeof(ProBuilderMenuActionAttribute)))
|
||||
return false;
|
||||
|
||||
var constructors = x.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
|
||||
if (!constructors.Any(y => y.GetParameters().Length < 1))
|
||||
{
|
||||
Log.Error("{0} type does not contain a parameterless constructor. Only parameterless constructors are invoked with creating MenuItem instances.", x.ToString());
|
||||
return false;
|
||||
}
|
||||
|
||||
if (constructors.Any(z => z.GetParameters().Length > 0))
|
||||
Log.Warning("{0} type contains a non-parameterless constructor. Only parameterless constructors are invoked with creating MenuItem instances.", x.ToString());
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
list.AddRange(menuActionTypes.Select(x => (MenuAction)Activator.CreateInstance(x)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
// some assemblies cannot be reflected (jetbrains plugin, for example)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ff8eab1edc549c8489031e8b952c1b56
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+1169
File diff suppressed because it is too large
Load Diff
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2e82491da45686848bf17df39975ff3c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,490 @@
|
||||
// #define DEBUG_MESH_SYNC
|
||||
#pragma warning disable 0168
|
||||
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.Rendering;
|
||||
using UObject = UnityEngine.Object;
|
||||
using UnityEditor.SettingsManagement;
|
||||
using UnityEditorInternal;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Debug = UnityEngine.Debug;
|
||||
|
||||
using PrefabStageUtility = UnityEditor.SceneManagement.PrefabStageUtility;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains utilities for working in the Unity Editor.
|
||||
/// </summary>
|
||||
public static class EditorUtility
|
||||
{
|
||||
const float k_DefaultNotificationDuration = 1f;
|
||||
static float s_NotificationTimer;
|
||||
static EditorWindow s_NotificationWindow;
|
||||
static bool s_IsNotificationDisplayed;
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_IsNotificationDisplayed = false;
|
||||
EditorApplication.update -= NotifUpdate;
|
||||
if (s_NotificationWindow != null)
|
||||
{
|
||||
// Window may be destroyed when entering Play Mode without domain reload.
|
||||
try { s_NotificationWindow.RemoveNotification(); }
|
||||
catch {}
|
||||
}
|
||||
|
||||
s_NotificationWindow = null;
|
||||
s_NotificationTimer = 0f;
|
||||
}
|
||||
|
||||
[UserSetting("General", "Show Action Notifications", "Enable or disable notification popups when performing actions.")]
|
||||
static Pref<bool> s_ShowNotifications = new Pref<bool>("editor.showEditorNotifications", false);
|
||||
|
||||
[UserSetting("Mesh Settings", "Static Editor Flags", "Default static flags to apply to new shapes.")]
|
||||
static Pref<StaticEditorFlags> s_StaticEditorFlags = new Pref<StaticEditorFlags>("mesh.defaultStaticEditorFlags", 0);
|
||||
|
||||
[UserSetting("Mesh Settings", "Mesh Collider is Convex", "If a MeshCollider is set as the default collider component, this sets the convex setting.")]
|
||||
static Pref<bool> s_MeshColliderIsConvex = new Pref<bool>("mesh.meshColliderIsConvex", false);
|
||||
|
||||
[UserSetting("Mesh Settings", "Snap New Shape To Grid", "When enabled, new shapes will snap to the closest point on grid.")]
|
||||
static Pref<bool> s_SnapNewShapesToGrid = new Pref<bool>("mesh.newShapesSnapToGrid", true);
|
||||
|
||||
[UserSetting("Mesh Settings", "Shadow Casting Mode", "The default ShadowCastingMode to apply to MeshRenderer components.")]
|
||||
static Pref<ShadowCastingMode> s_ShadowCastingMode = new Pref<ShadowCastingMode>("mesh.shadowCastingMode", ShadowCastingMode.On);
|
||||
|
||||
[UserSetting("Mesh Settings", "Collider Type", "What type of Collider to apply to new Shapes.")]
|
||||
static Pref<ColliderType> s_ColliderType = new Pref<ColliderType>("mesh.newShapeColliderType", ColliderType.MeshCollider);
|
||||
|
||||
/// <summary>
|
||||
/// Raised when a new mesh has been created and initialized through ProBuilder.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is only called when a user creates an object in the Editor using a ProBuilder menu item.
|
||||
/// </remarks>
|
||||
public static event Action<ProBuilderMesh> meshCreated = null;
|
||||
|
||||
/// <summary>
|
||||
/// Set the selected render state for an object. In Unity 5.4 and lower, this just toggles wireframe on or off.
|
||||
/// </summary>
|
||||
/// <param name="renderer"></param>
|
||||
/// <param name="state"></param>
|
||||
internal static void SetSelectionRenderState(Renderer renderer, EditorSelectedRenderState state)
|
||||
{
|
||||
UnityEditor.EditorUtility.SetSelectedRenderState(renderer, state);
|
||||
}
|
||||
|
||||
internal static void ShowNotification(ActionResult result)
|
||||
{
|
||||
ShowNotification(result.notification);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Show a timed (1 second) notification in the SceneView window.
|
||||
/// </summary>
|
||||
/// <param name="message">The text to display in the notification.</param>
|
||||
/// <seealso cref="RemoveNotification"/>
|
||||
internal static void ShowNotification(string message)
|
||||
{
|
||||
SceneView scnview = SceneView.lastActiveSceneView;
|
||||
if (scnview == null)
|
||||
scnview = EditorWindow.GetWindow<SceneView>();
|
||||
|
||||
ShowNotification(scnview, message);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ShowNotification(string)"/>
|
||||
/// <param name="window">The EditorWindow to display this notification in.</param>
|
||||
/// <param name="message">The text to display in the notification.</param>
|
||||
/// <exception cref="ArgumentNullException">Window is null.</exception>
|
||||
internal static void ShowNotification(EditorWindow window, string message)
|
||||
{
|
||||
if (!s_ShowNotifications)
|
||||
return;
|
||||
|
||||
if (window == null)
|
||||
throw new ArgumentNullException("window");
|
||||
|
||||
window.ShowNotification(new GUIContent(message, ""));
|
||||
window.Repaint();
|
||||
|
||||
if (EditorApplication.update != NotifUpdate)
|
||||
EditorApplication.update += NotifUpdate;
|
||||
|
||||
s_NotificationTimer = Time.realtimeSinceStartup + k_DefaultNotificationDuration;
|
||||
s_NotificationWindow = window;
|
||||
s_IsNotificationDisplayed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remove any currently displaying notifications from an <see cref="UnityEditor.EditorWindow"/>.
|
||||
/// </summary>
|
||||
/// <param name="window">The EditorWindow from which all currently displayed notifications will be removed.</param>
|
||||
/// <exception cref="ArgumentNullException">Thrown if window is null.</exception>
|
||||
internal static void RemoveNotification(EditorWindow window)
|
||||
{
|
||||
if (window == null)
|
||||
throw new ArgumentNullException("window");
|
||||
|
||||
EditorApplication.update -= NotifUpdate;
|
||||
|
||||
window.RemoveNotification();
|
||||
window.Repaint();
|
||||
}
|
||||
|
||||
static void NotifUpdate()
|
||||
{
|
||||
if (s_IsNotificationDisplayed && Time.realtimeSinceStartup > s_NotificationTimer)
|
||||
{
|
||||
s_IsNotificationDisplayed = false;
|
||||
RemoveNotification(s_NotificationWindow);
|
||||
}
|
||||
}
|
||||
|
||||
internal static bool IsPrefab(ProBuilderMesh mesh)
|
||||
{
|
||||
return PrefabUtility.GetPrefabAssetType(mesh.gameObject) != PrefabAssetType.NotAPrefab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if this object is a prefab instanced in the scene.
|
||||
/// </summary>
|
||||
/// <param name="go"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool IsPrefabInstance(GameObject go)
|
||||
{
|
||||
var status = PrefabUtility.GetPrefabInstanceStatus(go);
|
||||
#pragma warning disable 612, 618
|
||||
return status == PrefabInstanceStatus.Connected || status == PrefabInstanceStatus.Disconnected;
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this object is a prefab in the Project view.
|
||||
*/
|
||||
internal static bool IsPrefabAsset(GameObject go)
|
||||
{
|
||||
return PrefabUtility.IsPartOfPrefabAsset(go);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if Asset Store window is open, false otherwise.
|
||||
*/
|
||||
internal static bool AssetStoreWindowIsOpen()
|
||||
{
|
||||
return Resources.FindObjectsOfTypeAll<EditorWindow>().Any(x => x.GetType().ToString().Contains("AssetStoreWindow"));
|
||||
}
|
||||
|
||||
[Conditional("DEBUG_MESH_SYNC")]
|
||||
static void LogMeshSyncEvent(ProBuilderMesh mesh, MeshSyncState state, string msg)
|
||||
{
|
||||
Debug.Log($"{mesh} {mesh.GetObjectId()} {state} {msg}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether this object has a valid mesh reference, and the geometry is current. If the check fails,
|
||||
/// this function attempts to repair the sync state.
|
||||
/// </summary>
|
||||
/// <param name="mesh">The mesh component to test.</param>
|
||||
/// <seealso cref="ProBuilderMesh.meshSyncState"/>
|
||||
public static void SynchronizeWithMeshFilter(ProBuilderMesh mesh)
|
||||
{
|
||||
if (mesh == null)
|
||||
throw new ArgumentNullException(nameof(mesh));
|
||||
|
||||
mesh.EnsureMeshFilterIsAssigned();
|
||||
mesh.EnsureMeshColliderIsAssigned();
|
||||
MeshSyncState state = mesh.meshSyncState;
|
||||
bool meshesAreAssets = Experimental.meshesAreAssets;
|
||||
|
||||
if (state != MeshSyncState.InSync)
|
||||
{
|
||||
if (state == MeshSyncState.Null || state == MeshSyncState.NeedsRebuild)
|
||||
{
|
||||
LogMeshSyncEvent(mesh, state, "Rebuild");
|
||||
mesh.Rebuild();
|
||||
mesh.Optimize();
|
||||
}
|
||||
else
|
||||
{
|
||||
// old mesh didn't exist, so this is probably a prefab being instanced
|
||||
if (IsPrefabAsset(mesh.gameObject))
|
||||
{
|
||||
mesh.mesh.hideFlags = (HideFlags)(1 | 2 | 4 | 8);
|
||||
LogMeshSyncEvent(mesh, state, "Prefab, set HideFlags");
|
||||
}
|
||||
|
||||
mesh.Optimize();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (meshesAreAssets)
|
||||
EditorMeshUtility.TryCacheMesh(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if GameObject contains flags.
|
||||
/// </summary>
|
||||
/// <param name="go"></param>
|
||||
/// <param name="flags"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool HasStaticFlag(this GameObject go, StaticEditorFlags flags)
|
||||
{
|
||||
return (GameObjectUtility.GetStaticEditorFlags(go) & flags) == flags;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Move a GameObject to the proper active root.
|
||||
/// Checks if a default parent exists, otherwise it adds the object as a root of the active scene, which can be a prefab stage.
|
||||
/// </summary>
|
||||
/// <param name="gameObject"></param>
|
||||
internal static void MoveToActiveRoot(GameObject gameObject)
|
||||
{
|
||||
var parent = SceneView.GetDefaultParentObjectIfSet();
|
||||
if (parent != null)
|
||||
{
|
||||
gameObject.transform.SetParent(parent);
|
||||
return;
|
||||
}
|
||||
var prefabStage = PrefabStageUtility.GetCurrentPrefabStage();
|
||||
var activeScene = SceneManager.GetActiveScene();
|
||||
|
||||
if (prefabStage != null)
|
||||
{
|
||||
if (gameObject.scene != prefabStage.scene)
|
||||
{
|
||||
SceneManager.MoveGameObjectToScene(gameObject, prefabStage.scene);
|
||||
|
||||
// Prefabs cannot have multiple roots
|
||||
gameObject.transform.SetParent(prefabStage.prefabContentsRoot.transform, true);
|
||||
}
|
||||
}
|
||||
else if(gameObject.scene != activeScene)
|
||||
{
|
||||
gameObject.transform.SetParent(null);
|
||||
SceneManager.MoveGameObjectToScene(gameObject, activeScene);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initialize this object with the various editor-only parameters, and invoke the object creation callback.
|
||||
/// </summary>
|
||||
/// <param name="pb"></param>
|
||||
internal static void InitObject(ProBuilderMesh pb)
|
||||
{
|
||||
MoveToActiveRoot(pb.gameObject);
|
||||
|
||||
GameObjectUtility.EnsureUniqueNameForSibling(pb.gameObject);
|
||||
ScreenCenter(pb.gameObject);
|
||||
SnapInstantiatedObject(pb);
|
||||
|
||||
ComponentUtility.MoveComponentRelativeToComponent(pb, pb.transform, false);
|
||||
pb.renderer.shadowCastingMode = s_ShadowCastingMode;
|
||||
pb.renderer.sharedMaterial = EditorMaterialUtility.GetUserMaterial();
|
||||
|
||||
GameObjectUtility.SetStaticEditorFlags(pb.gameObject, s_StaticEditorFlags);
|
||||
|
||||
switch (s_ColliderType.value)
|
||||
{
|
||||
case ColliderType.BoxCollider:
|
||||
if(!pb.gameObject.TryGetComponent<BoxCollider>(out _))
|
||||
Undo.AddComponent(pb.gameObject, typeof(BoxCollider));
|
||||
break;
|
||||
|
||||
case ColliderType.MeshCollider:
|
||||
MeshCollider collider;
|
||||
if (!pb.gameObject.TryGetComponent<MeshCollider>(out collider))
|
||||
collider = Undo.AddComponent<MeshCollider>(pb.gameObject);
|
||||
// This little dance is required to prevent the Prefab system from detecting an overridden property
|
||||
// before ProBuilderMesh.RefreshCollisions has a chance to mark the MeshCollider.sharedMesh property
|
||||
// as driven. "AddComponent<MeshCollider>" constructs the MeshCollider and simultaneously assigns
|
||||
// the "m_Mesh" property, marking the property dirty. So we undo that change here, then assign the
|
||||
// mesh through our own method.
|
||||
collider.sharedMesh = null;
|
||||
collider.convex = s_MeshColliderIsConvex;
|
||||
pb.Refresh(RefreshMask.Collisions);
|
||||
break;
|
||||
}
|
||||
|
||||
pb.unwrapParameters = new UnwrapParameters(Lightmapping.s_UnwrapParameters);
|
||||
pb.Optimize();
|
||||
|
||||
// PBLD-137 - if Resident Drawer is on, it will start throwing errors if submeshCount != materialCount
|
||||
if (pb.mesh.subMeshCount == 0)
|
||||
pb.renderer.sharedMaterial = null;
|
||||
|
||||
if (meshCreated != null)
|
||||
meshCreated(pb);
|
||||
}
|
||||
|
||||
// If s_SnapNewShapesToGrid is enabled, always snap to the grid size. If it is not, use the active snap settings
|
||||
internal static void SnapInstantiatedObject(ProBuilderMesh mesh)
|
||||
{
|
||||
mesh.transform.position = ProBuilderSnapping.Snap(
|
||||
mesh.transform.position,
|
||||
s_SnapNewShapesToGrid
|
||||
? EditorSnapping.worldSnapMoveValue
|
||||
: EditorSnapping.activeMoveSnapValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the selected gameObject at the pivot point of the SceneView camera.
|
||||
*/
|
||||
internal static void ScreenCenter(GameObject _gameObject)
|
||||
{
|
||||
if (_gameObject == null)
|
||||
return;
|
||||
|
||||
// If in the unity editor, attempt to center the object the sceneview or main camera, in that order
|
||||
_gameObject.transform.position = ScenePivot();
|
||||
|
||||
Selection.activeObject = _gameObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current SceneView's camera's pivot point.
|
||||
*/
|
||||
internal static Vector3 ScenePivot()
|
||||
{
|
||||
return GetSceneView().pivot;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last active SceneView window, or creates a new one if no last SceneView is found.
|
||||
*/
|
||||
internal static SceneView GetSceneView()
|
||||
{
|
||||
return SceneView.lastActiveSceneView == null ? EditorWindow.GetWindow<SceneView>() : SceneView.lastActiveSceneView;
|
||||
}
|
||||
|
||||
internal static bool IsUnix()
|
||||
{
|
||||
System.PlatformID platform = System.Environment.OSVersion.Platform;
|
||||
return platform == System.PlatformID.MacOSX ||
|
||||
platform == System.PlatformID.Unix ||
|
||||
(int)platform == 128;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Is this mode one of the mesh element modes (vertex, edge, face, texture).
|
||||
/// </summary>
|
||||
/// <param name="mode"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool IsMeshElementMode(this SelectMode mode)
|
||||
{
|
||||
return mode.ContainsFlag(
|
||||
SelectMode.Vertex
|
||||
| SelectMode.Edge
|
||||
| SelectMode.Face
|
||||
| SelectMode.TextureEdge
|
||||
| SelectMode.TextureFace
|
||||
| SelectMode.TextureVertex
|
||||
);
|
||||
}
|
||||
|
||||
internal static bool IsTextureMode(this SelectMode mode)
|
||||
{
|
||||
return mode.ContainsFlag(
|
||||
SelectMode.TextureEdge
|
||||
| SelectMode.TextureFace
|
||||
| SelectMode.TextureVertex
|
||||
);
|
||||
}
|
||||
|
||||
internal static bool IsPositionMode(this SelectMode mode)
|
||||
{
|
||||
return mode.ContainsFlag(
|
||||
SelectMode.Edge
|
||||
| SelectMode.Face
|
||||
| SelectMode.Vertex
|
||||
);
|
||||
}
|
||||
|
||||
internal static SelectMode GetPositionMode(this SelectMode mode)
|
||||
{
|
||||
if (mode.ContainsFlag(SelectMode.TextureFace))
|
||||
mode = (mode & ~SelectMode.TextureFace) | SelectMode.Face;
|
||||
|
||||
if (mode.ContainsFlag(SelectMode.TextureEdge))
|
||||
mode = (mode & ~SelectMode.TextureEdge) | SelectMode.Edge;
|
||||
|
||||
if (mode.ContainsFlag(SelectMode.TextureVertex))
|
||||
mode = (mode & ~SelectMode.TextureVertex) | SelectMode.Vertex;
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
internal static SelectMode GetTextureMode(this SelectMode mode)
|
||||
{
|
||||
if (mode.ContainsFlag(SelectMode.Face))
|
||||
mode = (mode & ~SelectMode.Face) | SelectMode.TextureFace;
|
||||
|
||||
if (mode.ContainsFlag(SelectMode.Edge))
|
||||
mode = (mode & ~SelectMode.Edge) | SelectMode.TextureEdge;
|
||||
|
||||
if (mode.ContainsFlag(SelectMode.Vertex))
|
||||
mode = (mode & ~SelectMode.Vertex) | SelectMode.TextureVertex;
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test if SelectMode contains any of the value bits.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// HasFlag doesn't exist in .NET 3.5
|
||||
/// </remarks>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="value"></param>
|
||||
/// <returns></returns>
|
||||
internal static bool ContainsFlag(this SelectMode target, SelectMode value)
|
||||
{
|
||||
return (target & value) != 0;
|
||||
}
|
||||
|
||||
internal static bool IsDeveloperMode()
|
||||
{
|
||||
return EditorPrefs.GetBool("DeveloperMode", false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches a scene gizmo to the mesh in the Unity Editor.
|
||||
/// </summary>
|
||||
/// <param name="script">The Type representing the class of the item to attach the gizmo to (for example, the ProBuilderMesh).</param>
|
||||
/// <param name="enabled">True to render the gizmo (for example, while selected); false to not render it.</param>
|
||||
public static void SetGizmoIconEnabled(Type script, bool enabled)
|
||||
{
|
||||
var annotations = AnnotationUtility.GetAnnotations();
|
||||
var annotation = annotations.FirstOrDefault(x => x.scriptClass.Contains(script.Name));
|
||||
AnnotationUtility.SetIconEnabled(annotation.classID, annotation.scriptClass, enabled ? 1 : 0);
|
||||
}
|
||||
|
||||
internal static T[] FindObjectsByType<T>() where T : UObject
|
||||
{
|
||||
#if UNITY_6000_4_OR_NEWER
|
||||
return UObject.FindObjectsByType<T>();
|
||||
#else
|
||||
return UObject.FindObjectsByType<T>(FindObjectsSortMode.None);
|
||||
#endif
|
||||
}
|
||||
|
||||
internal static string GetActiveSceneAssetsPath()
|
||||
{
|
||||
const string k_SavedMeshPath = "Assets/ProBuilder Data/Saved Meshes";
|
||||
var scene = SceneManager.GetActiveScene();
|
||||
var path = string.IsNullOrEmpty(scene.path)
|
||||
? k_SavedMeshPath
|
||||
: $"{Path.GetDirectoryName(scene.path)}/{scene.name}/ProBuilder Meshes";
|
||||
Directory.CreateDirectory(path);
|
||||
return path;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a17a8a25b5ef051479efd66b56f58823
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,154 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
[CustomEditor(typeof(Entity))]
|
||||
[CanEditMultipleObjects]
|
||||
sealed class EntityEditor : Editor
|
||||
{
|
||||
Entity ent;
|
||||
ProBuilderMesh pb;
|
||||
|
||||
public enum ColType
|
||||
{
|
||||
MeshCollider,
|
||||
BoxCollider,
|
||||
SphereCollider
|
||||
}
|
||||
|
||||
public void OnEnable()
|
||||
{
|
||||
ent = (Entity)target;
|
||||
|
||||
if (ent != null)
|
||||
pb = (ProBuilderMesh)ent.transform.GetComponent<ProBuilderMesh>();
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
if (pb == null) return;
|
||||
if (ent == null) return;
|
||||
|
||||
EntityType et = ent.entityType;
|
||||
et = (EntityType)EditorGUILayout.EnumPopup("Entity Type", et);
|
||||
if (et != ent.entityType)
|
||||
{
|
||||
UndoUtility.RecordObjects(new Object[] {ent, ent.gameObject.GetComponent<ProBuilderMesh>() }, "Set Entity Type");
|
||||
#pragma warning disable 0618
|
||||
EntityUtility.SetEntityType(et, ent.gameObject);
|
||||
#pragma warning restore 0618
|
||||
pb.ToMesh();
|
||||
pb.Refresh();
|
||||
pb.Optimize();
|
||||
}
|
||||
|
||||
GUILayout.Space(4);
|
||||
|
||||
pb.userCollisions = EditorGUILayout.Toggle("Custom Collider", pb.userCollisions);
|
||||
|
||||
// Convience
|
||||
if (pb.userCollisions)
|
||||
GUI.enabled = false;
|
||||
|
||||
GUILayout.Label("Add Collider", EditorStyles.boldLabel);
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
if (GUILayout.Button("Mesh Collider", EditorStyles.miniButtonLeft))
|
||||
EditorApplication.delayCall += AddMeshCollider;
|
||||
|
||||
if (GUILayout.Button("Box Collider", EditorStyles.miniButtonMid))
|
||||
EditorApplication.delayCall += AddBoxCollider;
|
||||
|
||||
if (GUILayout.Button("Remove Collider", EditorStyles.miniButtonRight))
|
||||
EditorApplication.delayCall += RemoveColliders;
|
||||
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUI.enabled = true;
|
||||
}
|
||||
|
||||
void AddMeshCollider()
|
||||
{
|
||||
AddCollider(ColType.MeshCollider);
|
||||
}
|
||||
|
||||
void AddBoxCollider()
|
||||
{
|
||||
AddCollider(ColType.BoxCollider);
|
||||
}
|
||||
|
||||
private void AddCollider(ColType c)
|
||||
{
|
||||
Collider[] colliders = serializedObject.targetObjects.Where(x => x is Entity).SelectMany(x => ((Entity)x).gameObject.GetComponents<Collider>()).ToArray();
|
||||
bool isTrigger = false;
|
||||
if (colliders != null)
|
||||
isTrigger = colliders.Any(x => x.isTrigger);
|
||||
|
||||
RemoveColliders();
|
||||
|
||||
foreach (Entity obj in serializedObject.targetObjects)
|
||||
{
|
||||
GameObject go = obj.gameObject;
|
||||
|
||||
switch (c)
|
||||
{
|
||||
case ColType.MeshCollider:
|
||||
{
|
||||
MeshCollider col = go.AddComponent<MeshCollider>();
|
||||
|
||||
if (ent.entityType == EntityType.Trigger)
|
||||
{
|
||||
col.convex = true;
|
||||
col.isTrigger = true;
|
||||
}
|
||||
else if (ent.entityType == EntityType.Collider)
|
||||
{
|
||||
col.convex = true;
|
||||
}
|
||||
else if (isTrigger)
|
||||
{
|
||||
col.convex = true;
|
||||
col.isTrigger = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case ColType.BoxCollider:
|
||||
{
|
||||
BoxCollider col = go.AddComponent<BoxCollider>();
|
||||
|
||||
if (ent.entityType == EntityType.Trigger || isTrigger)
|
||||
col.isTrigger = true;
|
||||
break;
|
||||
}
|
||||
|
||||
case ColType.SphereCollider:
|
||||
{
|
||||
SphereCollider col = go.AddComponent<SphereCollider>();
|
||||
if (ent.entityType == EntityType.Trigger || isTrigger)
|
||||
col.isTrigger = true;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveColliders()
|
||||
{
|
||||
foreach (Entity obj in serializedObject.targetObjects)
|
||||
{
|
||||
foreach (Collider c in obj.gameObject.GetComponents<Collider>())
|
||||
DestroyImmediate(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b0c5bae1215e224409318f415e45db0a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
static class EntityUtility
|
||||
{
|
||||
const StaticEditorFlags StaticEditorFlags_All =
|
||||
StaticEditorFlags.ContributeGI |
|
||||
StaticEditorFlags.OccluderStatic |
|
||||
StaticEditorFlags.BatchingStatic |
|
||||
StaticEditorFlags.OccludeeStatic |
|
||||
StaticEditorFlags.ReflectionProbeStatic;
|
||||
|
||||
/// <summary>
|
||||
/// Sets the EntityType for the passed gameObject.
|
||||
/// </summary>
|
||||
/// <param name="newEntityType">The type to set.</param>
|
||||
/// <param name="target">The gameObject to apply the EntityType to. Must contains pb_Object and pb_Entity components. Method does contain null checks.</param>
|
||||
[Obsolete("pb_Entity is deprecated. Manage static flags manually or use Set Trigger/Set Collider actions.")]
|
||||
public static void SetEntityType(EntityType newEntityType, GameObject target)
|
||||
{
|
||||
if (!target.TryGetComponent<Entity>(out var ent))
|
||||
ent = target.AddComponent<Entity>();
|
||||
|
||||
ProBuilderMesh pb = target.GetComponent<ProBuilderMesh>();
|
||||
|
||||
if (!ent || !pb)
|
||||
return;
|
||||
|
||||
SetEditorFlags(StaticEditorFlags_All, target);
|
||||
|
||||
switch (newEntityType)
|
||||
{
|
||||
case EntityType.Detail:
|
||||
case EntityType.Occluder:
|
||||
SetBrush(target);
|
||||
break;
|
||||
|
||||
case EntityType.Trigger:
|
||||
SetTrigger(target);
|
||||
break;
|
||||
|
||||
case EntityType.Collider:
|
||||
SetCollider(target);
|
||||
break;
|
||||
|
||||
case EntityType.Mover:
|
||||
SetDynamic(target);
|
||||
break;
|
||||
}
|
||||
|
||||
ent.SetEntity(newEntityType);
|
||||
}
|
||||
|
||||
[Obsolete("pb_Entity is deprecated. Manage static flags manually or use Set Trigger/Set Collider actions.")]
|
||||
static void SetBrush(GameObject target)
|
||||
{
|
||||
EntityType et = target.GetComponent<Entity>().entityType;
|
||||
|
||||
if (et == EntityType.Trigger ||
|
||||
et == EntityType.Collider)
|
||||
{
|
||||
ProBuilderMesh pb = target.GetComponent<ProBuilderMesh>();
|
||||
foreach (var face in pb.facesInternal)
|
||||
face.material = BuiltinMaterials.defaultMaterial;
|
||||
pb.ToMesh();
|
||||
pb.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("pb_Entity is deprecated. Manage static flags manually or use Set Trigger/Set Collider actions.")]
|
||||
static void SetDynamic(GameObject target)
|
||||
{
|
||||
EntityType et = target.GetComponent<Entity>().entityType;
|
||||
|
||||
SetEditorFlags((StaticEditorFlags)0, target);
|
||||
|
||||
if (et == EntityType.Trigger ||
|
||||
et == EntityType.Collider)
|
||||
{
|
||||
ProBuilderMesh pb = target.GetComponent<ProBuilderMesh>();
|
||||
foreach (var face in pb.facesInternal)
|
||||
face.material = BuiltinMaterials.defaultMaterial;
|
||||
|
||||
pb.ToMesh();
|
||||
pb.Refresh();
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete("pb_Entity is deprecated. Manage static flags manually or use Set Trigger/Set Collider actions.")]
|
||||
static void SetTrigger(GameObject target)
|
||||
{
|
||||
ProBuilderMesh pb = target.GetComponent<ProBuilderMesh>();
|
||||
foreach (var face in pb.facesInternal)
|
||||
face.material = BuiltinMaterials.triggerMaterial;
|
||||
SetIsTrigger(true, target);
|
||||
SetEditorFlags((StaticEditorFlags)0, target);
|
||||
|
||||
pb.ToMesh();
|
||||
pb.Refresh();
|
||||
}
|
||||
|
||||
[Obsolete("pb_Entity is deprecated. Manage static flags manually or use Set Trigger/Set Collider actions.")]
|
||||
static void SetCollider(GameObject target)
|
||||
{
|
||||
ProBuilderMesh pb = target.GetComponent<ProBuilderMesh>();
|
||||
foreach (var face in pb.facesInternal)
|
||||
face.material = BuiltinMaterials.colliderMaterial;
|
||||
pb.ToMesh();
|
||||
pb.Refresh();
|
||||
|
||||
SetEditorFlags((StaticEditorFlags)(StaticEditorFlags.NavigationStatic | StaticEditorFlags.OffMeshLinkGeneration),
|
||||
target);
|
||||
}
|
||||
|
||||
[Obsolete("pb_Entity is deprecated. Manage static flags manually or use Set Trigger/Set Collider actions.")]
|
||||
static void SetEditorFlags(StaticEditorFlags editorFlags, GameObject target)
|
||||
{
|
||||
GameObjectUtility.SetStaticEditorFlags(target, editorFlags);
|
||||
}
|
||||
|
||||
[Obsolete("pb_Entity is deprecated. Manage static flags manually or use Set Trigger/Set Collider actions.")]
|
||||
static void SetIsTrigger(bool val, GameObject target)
|
||||
{
|
||||
Collider[] colliders = InternalUtility.GetComponents<Collider>(target);
|
||||
foreach (Collider col in colliders)
|
||||
{
|
||||
if (val && col is MeshCollider)
|
||||
((MeshCollider)col).convex = true;
|
||||
col.isTrigger = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4746bcae5f2c20c4db633d37d4a2901d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,75 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Responsible for managing the visibility of entity types in the scene.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
internal static class EntityVisibility
|
||||
{
|
||||
static EntityVisibility()
|
||||
{
|
||||
EditorApplication.playModeStateChanged += x => { OnPlayModeStateChanged(); };
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
}
|
||||
|
||||
private static void OnSceneLoaded(Scene scene, LoadSceneMode mode)
|
||||
{
|
||||
if (EditorApplication.isPlaying)
|
||||
{
|
||||
foreach (var rootGameObject in scene.GetRootGameObjects())
|
||||
{
|
||||
foreach (var entityBehaviour in rootGameObject.GetComponentsInChildren<EntityBehaviour>())
|
||||
{
|
||||
if (entityBehaviour.manageVisibility)
|
||||
entityBehaviour.OnSceneLoaded(scene, mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the visibility of an entity type in the Scene view.
|
||||
/// </summary>
|
||||
/// <param name="entityType"></param>
|
||||
/// <param name="isVisible"></param>
|
||||
public static void SetEntityVisibility(EntityType entityType, bool isVisible)
|
||||
{
|
||||
foreach (var entity in EditorUtility.FindObjectsByType<Entity>())
|
||||
{
|
||||
if (entity.entityType == entityType)
|
||||
{
|
||||
if (entity.TryGetComponent<MeshRenderer>(out var mr)) mr.enabled = isVisible;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registered to EditorApplication.onPlaymodeStateChanged
|
||||
/// </summary>
|
||||
private static void OnPlayModeStateChanged()
|
||||
{
|
||||
var isPlaying = EditorApplication.isPlaying;
|
||||
var orWillPlay = EditorApplication.isPlayingOrWillChangePlaymode;
|
||||
|
||||
// if these two don't match, that means it's the call prior to actually engaging
|
||||
// whatever state. when entering play mode it doesn't make a difference, but on
|
||||
// exiting it's the difference between a scene reload and the reloaded scene.
|
||||
if (isPlaying != orWillPlay)
|
||||
return;
|
||||
|
||||
var isEntering = isPlaying && orWillPlay;
|
||||
|
||||
foreach (var entityBehaviour in Resources.FindObjectsOfTypeAll<EntityBehaviour>())
|
||||
if (entityBehaviour.manageVisibility)
|
||||
if (isEntering)
|
||||
entityBehaviour.OnEnterPlayMode();
|
||||
|
||||
if (!isEntering)
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 171bb48d73467244c9487173c12e2c46
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
using UnityEditor.SettingsManagement;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
static class Experimental
|
||||
{
|
||||
const string k_ExperimentalFeaturesEnabled = "PROBUILDER_EXPERIMENTAL_FEATURES";
|
||||
|
||||
[UserSetting]
|
||||
static Pref<bool> s_experimentalFeatureEnabled = new Pref<bool>("experimental.enabled", false, SettingsScope.Project);
|
||||
|
||||
[UserSetting]
|
||||
static Pref<bool> s_MeshesAreAssets = new Pref<bool>("experimental.meshesAreAssets", false, SettingsScope.Project);
|
||||
|
||||
internal static bool meshesAreAssets
|
||||
{
|
||||
get { return experimentalFeaturesEnabled && s_MeshesAreAssets; }
|
||||
}
|
||||
|
||||
internal static bool experimentalFeaturesEnabled
|
||||
{
|
||||
#if PROBUILDER_EXPERIMENTAL_FEATURES
|
||||
get { return true; }
|
||||
#else
|
||||
get { return false; }
|
||||
#endif
|
||||
}
|
||||
|
||||
static Experimental()
|
||||
{
|
||||
if(s_experimentalFeatureEnabled.value != experimentalFeaturesEnabled)
|
||||
{
|
||||
s_experimentalFeatureEnabled.value = experimentalFeaturesEnabled;
|
||||
ProBuilderSettings.Save();
|
||||
}
|
||||
}
|
||||
|
||||
public static void AfterSettingsSaved()
|
||||
{
|
||||
#if PROBUILDER_EXPERIMENTAL_FEATURES
|
||||
if(!s_experimentalFeatureEnabled.value)
|
||||
ScriptingSymbolManager.RemoveScriptingDefine(k_ExperimentalFeaturesEnabled);
|
||||
#else
|
||||
if(s_experimentalFeatureEnabled.value)
|
||||
ScriptingSymbolManager.AddScriptingDefine(k_ExperimentalFeaturesEnabled);
|
||||
#endif
|
||||
}
|
||||
|
||||
[UserSettingBlock("Experimental")]
|
||||
static void ExperimentalFeaturesSettings(string searchContext)
|
||||
{
|
||||
var enabled = experimentalFeaturesEnabled;
|
||||
|
||||
EditorGUILayout.HelpBox("Enabling Experimental Features will cause Unity to recompile scripts.", MessageType.Warning);
|
||||
s_experimentalFeatureEnabled.value = SettingsGUILayout.SearchableToggle("Experimental Features Enabled", enabled, searchContext);
|
||||
|
||||
if(s_experimentalFeatureEnabled.value)
|
||||
{
|
||||
using (new SettingsGUILayout.IndentedGroup())
|
||||
{
|
||||
s_MeshesAreAssets.value = SettingsGUILayout.SettingsToggle("Store Mesh as Asset", s_MeshesAreAssets, searchContext);
|
||||
|
||||
if (s_MeshesAreAssets.value)
|
||||
EditorGUILayout.HelpBox("Please note that this feature is untested, and may result in instabilities or lost work. Proceed with caution!", MessageType.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2c71b118edf0476f868fa213864aac6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,300 @@
|
||||
using UnityEngine;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine.ProBuilder;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper functions for working with files and directories.
|
||||
/// </summary>
|
||||
static class FileUtility
|
||||
{
|
||||
// ProBuilder folder path.
|
||||
static string s_ProBuilderFolderPath = "Packages/com.unity.probuilder/";
|
||||
static string s_ProBuilderDataPath = "Assets/ProBuilder Data/";
|
||||
|
||||
// The order is important - always search for the package manager installed version first
|
||||
static readonly string[] k_PossibleInstallDirectories = new string[]
|
||||
{
|
||||
"Packages/com.unity.probuilder/",
|
||||
"UnityPackageManager/com.unity.probuilder/",
|
||||
"Assets/",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Check that the directory contains a valid ProBuilder install.
|
||||
/// </summary>
|
||||
/// <param name="dir">Directory to check</param>
|
||||
/// <returns></returns>
|
||||
internal static bool ValidateProBuilderRoot(string dir)
|
||||
{
|
||||
return !string.IsNullOrEmpty(dir) &&
|
||||
Directory.Exists(dir + "/Editor/EditorCore") &&
|
||||
Directory.Exists(dir + "/Runtime/Core") &&
|
||||
Directory.Exists(dir + "/Runtime/MeshOperations");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Return a relative path to the ProBuilder directory. Can be in the packages cache or Assets folder.
|
||||
/// If the project is in the packman cache it is immutable.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string GetProBuilderInstallDirectory()
|
||||
{
|
||||
if (ValidateProBuilderRoot(s_ProBuilderFolderPath))
|
||||
return s_ProBuilderFolderPath;
|
||||
|
||||
foreach (var install in k_PossibleInstallDirectories)
|
||||
{
|
||||
s_ProBuilderFolderPath = install;
|
||||
|
||||
if (ValidateProBuilderRoot(s_ProBuilderFolderPath))
|
||||
return s_ProBuilderFolderPath;
|
||||
}
|
||||
|
||||
// It's not in any of the usual haunts, start digging through Assets until we find it (likely an A$ install)
|
||||
s_ProBuilderFolderPath = FindAssetStoreProBuilderInstall();
|
||||
|
||||
if (Directory.Exists(s_ProBuilderFolderPath))
|
||||
return s_ProBuilderFolderPath;
|
||||
|
||||
// Things are dire. ProBuilder was nowhere to be found in the Assets directory, which means either the user
|
||||
// has renamed the folder, or something very spooky is going on.
|
||||
// Either way, just create a new ProBuilder folder in Assets and return that so at the very least
|
||||
// local preferences and the material/color palettes will still work.
|
||||
Log.Warning("Creating a new ProBuilder directory... was the ProBuilder folder renamed?\nIcons & preferences may not work in this state.");
|
||||
s_ProBuilderFolderPath = "Assets/ProBuilder";
|
||||
Directory.CreateDirectory(s_ProBuilderFolderPath);
|
||||
|
||||
return s_ProBuilderFolderPath;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scan the Assets directory for an install of ProBuilder.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string FindAssetStoreProBuilderInstall()
|
||||
{
|
||||
string dir = null;
|
||||
|
||||
string[] matches = Directory.GetDirectories("Assets", "ProBuilder", SearchOption.AllDirectories);
|
||||
|
||||
foreach (var match in matches)
|
||||
{
|
||||
dir = match.Replace("\\", "/") + "/";
|
||||
if (dir.Contains("ProBuilder") && ValidateProBuilderRoot(dir))
|
||||
break;
|
||||
}
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the path to the local ProBuilder/Data folder
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string GetLocalDataDirectory(bool initializeIfMissing = false)
|
||||
{
|
||||
if (Directory.Exists(s_ProBuilderDataPath))
|
||||
return s_ProBuilderDataPath;
|
||||
|
||||
string root = GetProBuilderInstallDirectory();
|
||||
|
||||
if (root.StartsWith("Assets"))
|
||||
{
|
||||
// Installed from Asset Store or manual package import
|
||||
s_ProBuilderDataPath = root + "Data/";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Scan project for ProBuilder Data folder
|
||||
// none found? create one at root
|
||||
string[] matches = Directory.GetDirectories("Assets", "ProBuilder Data", SearchOption.AllDirectories);
|
||||
s_ProBuilderDataPath = matches.Length > 0 ? matches[0] : "Assets/ProBuilder Data/";
|
||||
}
|
||||
|
||||
if (!Directory.Exists(s_ProBuilderDataPath) && initializeIfMissing)
|
||||
Directory.CreateDirectory(s_ProBuilderDataPath);
|
||||
|
||||
return s_ProBuilderDataPath;
|
||||
}
|
||||
|
||||
internal static string[] FindAssets<T>(string pattern) where T : UnityEngine.Object
|
||||
{
|
||||
return AssetDatabase.FindAssets("t:" + typeof(T).ToString() + " " + pattern);
|
||||
}
|
||||
|
||||
internal static T[] FindAndLoadAssets<T>() where T : UnityEngine.Object
|
||||
{
|
||||
return AssetDatabase.FindAssets("t:" + typeof(T).ToString())
|
||||
.Select(x => AssetDatabase.LoadAssetAtPath<T>(AssetDatabase.GUIDToAssetPath(x))).ToArray();
|
||||
}
|
||||
|
||||
internal static T FindAssetOfType<T>() where T : UnityEngine.Object
|
||||
{
|
||||
foreach (var i in AssetDatabase.FindAssets("t:" + typeof(T).ToString()))
|
||||
{
|
||||
T o = AssetDatabase.LoadAssetAtPath<T>(AssetDatabase.GUIDToAssetPath(i));
|
||||
if (o != null)
|
||||
return o;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the selected directory relative to project root.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal static string GetSelectedDirectory()
|
||||
{
|
||||
Object o = Selection.activeObject;
|
||||
|
||||
if (o != null)
|
||||
{
|
||||
#if UNITY_6000_4_OR_NEWER
|
||||
string path = AssetDatabase.GetAssetPath(o.GetEntityId());
|
||||
#else
|
||||
#pragma warning disable 0618 // Marked obsolete on 6.3
|
||||
string path = AssetDatabase.GetAssetPath(o.GetInstanceID());
|
||||
#pragma warning restore 0618
|
||||
#endif
|
||||
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
return GetRelativePath(Path.GetFullPath(path));
|
||||
|
||||
string res = Path.GetDirectoryName(path);
|
||||
|
||||
if (!string.IsNullOrEmpty(res) && Directory.Exists(res))
|
||||
return GetRelativePath(Path.GetFullPath(res));
|
||||
}
|
||||
}
|
||||
|
||||
return "Assets";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a file or folder path relative to the Unity project directory.
|
||||
/// </summary>
|
||||
/// <param name="path">File or directory path, either relative or absolute.</param>
|
||||
/// <returns>A new path relative to the current project root.</returns>
|
||||
public static string GetRelativePath(string path)
|
||||
{
|
||||
string full = Path.GetFullPath(path).Replace("\\", "/");
|
||||
string cur = Directory.GetCurrentDirectory().Replace("\\", "/");
|
||||
if (!cur.EndsWith("/"))
|
||||
cur += "/";
|
||||
return full.Replace(cur, "");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if a file or folder exists at path.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <returns></returns>
|
||||
public static bool Exists(string path)
|
||||
{
|
||||
return Directory.Exists(path) || File.Exists(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load an internal asset relative to the ProBuilder directory.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
internal static T LoadInternalAsset<T>(string path) where T : Object
|
||||
{
|
||||
string full = string.Format("{0}{1}", GetProBuilderInstallDirectory(), path);
|
||||
return Load<T>(full);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetch a default asset from path. If not found, a new one is created.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
internal static T LoadRequired<T>(string path) where T : ScriptableObject, IHasDefault
|
||||
{
|
||||
T asset = Load<T>(path);
|
||||
|
||||
if (asset == null)
|
||||
{
|
||||
asset = ScriptableObject.CreateInstance<T>();
|
||||
|
||||
asset.SetDefaultValues();
|
||||
|
||||
UnityEditor.EditorUtility.SetDirty(asset);
|
||||
|
||||
string folder = Path.GetDirectoryName(path);
|
||||
|
||||
if (!Directory.Exists(folder))
|
||||
Directory.CreateDirectory(folder);
|
||||
|
||||
AssetDatabase.CreateAsset(asset, path);
|
||||
}
|
||||
|
||||
return asset;
|
||||
}
|
||||
|
||||
static T Load<T>(string path) where T : Object
|
||||
{
|
||||
return AssetDatabase.LoadAssetAtPath<T>(path);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write contents to a file path.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="contents"></param>
|
||||
[System.Obsolete("Use WriteAllText")]
|
||||
public static void WriteFile(string path, string contents)
|
||||
{
|
||||
WriteAllText(path, contents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Write contents to a file path, creating a new directory if necessary.
|
||||
/// </summary>
|
||||
/// <param name="path"></param>
|
||||
/// <param name="contents"></param>
|
||||
public static void WriteAllText(string path, string contents)
|
||||
{
|
||||
string dir = Path.GetDirectoryName(path);
|
||||
|
||||
if (string.IsNullOrEmpty(dir))
|
||||
{
|
||||
Log.Error("Cannot write file to \"{0}\", invalid path.", path);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
File.WriteAllText(path, contents);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save an image to the specified path.
|
||||
/// </summary>
|
||||
/// <param name="texture"></param>
|
||||
/// <param name="path"></param>
|
||||
/// <returns>True on success, false if operation failed.</returns>
|
||||
internal static bool SaveTexture(Texture2D texture, string path)
|
||||
{
|
||||
byte[] bytes = texture.EncodeToPNG();
|
||||
|
||||
if (string.IsNullOrEmpty(path))
|
||||
return false;
|
||||
|
||||
System.IO.File.WriteAllBytes(path, bytes);
|
||||
AssetDatabase.Refresh();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b360b08870ee97249b28c408f0d1e0d8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
sealed class HandleGUI : IDisposable
|
||||
{
|
||||
public HandleGUI()
|
||||
{
|
||||
Handles.BeginGUI();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Handles.EndGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b73fbc8867750541be154ed40569dea
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Static delegates listen for hierarchy changes (duplication, delete, copy/paste) and rebuild the mesh components
|
||||
/// of pb_Objects if necessary.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
static class HierarchyListener
|
||||
{
|
||||
static HierarchyListener()
|
||||
{
|
||||
AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload;
|
||||
PrefabUtility.prefabInstanceUpdated += PrefabInstanceUpdated;
|
||||
PrefabUtility.prefabInstanceReverted += PrefabInstanceUpdated;
|
||||
PrefabUtility.prefabInstanceReverting += PrefabInstanceReverting;
|
||||
ProBuilderMesh.meshWasInitialized += OnMeshInitialized;
|
||||
ObjectChangeEvents.changesPublished += ObjectEventChangesPublished;
|
||||
}
|
||||
|
||||
static void ObjectEventChangesPublished(ref ObjectChangeEventStream stream)
|
||||
{
|
||||
for (int i = 0, c = stream.length; i < c; ++i)
|
||||
{
|
||||
var type = stream.GetEventType(i);
|
||||
|
||||
if (type == ObjectChangeKind.CreateGameObjectHierarchy)
|
||||
{
|
||||
stream.GetCreateGameObjectHierarchyEvent(i, out var data);
|
||||
var go = GetGameObject(data);
|
||||
if (go != null) CheckForProBuilderMeshesCreatedOrModified(go);
|
||||
}
|
||||
else if (type == ObjectChangeKind.ChangeGameObjectStructure)
|
||||
{
|
||||
stream.GetChangeGameObjectStructureEvent(i, out var data);
|
||||
var go = GetGameObject(data);
|
||||
if (go != null) CheckForProBuilderMeshesCreatedOrModified(go);
|
||||
}
|
||||
else if (type == ObjectChangeKind.ChangeGameObjectStructureHierarchy)
|
||||
{
|
||||
// Note 2 : This needs to be called when using a Prefab>Replace action in the menus to refresh the current
|
||||
// ProBuilder Mesh, this is still a problem in 2023.3 as it does not automatically refresh the mesh
|
||||
// Note 1 : that this is leaking meshes when reverting! in 2023.1+ we handle it correctly, but 2022 and
|
||||
// 2021 have the PPtr reset to the serialized value (null) before we have any access. orphaned
|
||||
// mesh assets are cleaned up on scene or domain reloads, so we'll live with it.
|
||||
stream.GetChangeGameObjectStructureHierarchyEvent(i, out var data);
|
||||
var go = GetGameObject(data);
|
||||
|
||||
if (go != null)
|
||||
{
|
||||
var meshes = go.GetComponentsInChildren<ProBuilderMesh>();
|
||||
foreach (var mesh in meshes)
|
||||
EditorUtility.SynchronizeWithMeshFilter(mesh);
|
||||
}
|
||||
ProBuilderEditor.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_6000_4_OR_NEWER
|
||||
static GameObject GetGameObject(CreateGameObjectHierarchyEventArgs data) => UnityEditor.EditorUtility.EntityIdToObject(data.entityId) as GameObject;
|
||||
static GameObject GetGameObject(ChangeGameObjectStructureEventArgs data) => UnityEditor.EditorUtility.EntityIdToObject(data.entityId) as GameObject;
|
||||
static GameObject GetGameObject(ChangeGameObjectStructureHierarchyEventArgs data) => UnityEditor.EditorUtility.EntityIdToObject(data.entityId) as GameObject;
|
||||
#else
|
||||
static GameObject GetGameObject(CreateGameObjectHierarchyEventArgs data) => InstanceIDToObject(data.instanceId);
|
||||
static GameObject GetGameObject(ChangeGameObjectStructureEventArgs data) => InstanceIDToObject(data.instanceId);
|
||||
static GameObject GetGameObject(ChangeGameObjectStructureHierarchyEventArgs data) => InstanceIDToObject(data.instanceId);
|
||||
|
||||
static GameObject InstanceIDToObject(int instanceId)
|
||||
{
|
||||
#pragma warning disable 0618 // Marked obsolete on 6.3
|
||||
return UnityEditor.EditorUtility.InstanceIDToObject(instanceId) as GameObject;
|
||||
#pragma warning restore 0618
|
||||
}
|
||||
#endif
|
||||
|
||||
static void CheckForProBuilderMeshesCreatedOrModified(GameObject go)
|
||||
{
|
||||
if(go.TryGetComponent<ProBuilderMesh>(out var mesh))
|
||||
OnObjectCreated(mesh);
|
||||
var childCount = go.transform.childCount;
|
||||
if (childCount > 0)
|
||||
{
|
||||
for(int childIndex = 0; childIndex < childCount; ++childIndex)
|
||||
CheckForProBuilderMeshesCreatedOrModified(go.transform.GetChild(childIndex).gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// used by tests
|
||||
internal static void OnObjectCreated(ProBuilderMesh mesh)
|
||||
{
|
||||
if (mesh.versionIndex != ProBuilderMesh.k_UnitializedVersionIndex &&
|
||||
mesh.nonSerializedVersionIndex == ProBuilderMesh.k_UnitializedVersionIndex)
|
||||
{
|
||||
mesh.MakeUnique();
|
||||
Undo.RegisterCompleteObjectUndo( mesh.gameObject, "Paste ProBuilderMesh");
|
||||
mesh.Optimize();
|
||||
}
|
||||
}
|
||||
|
||||
static void OnMeshInitialized(ProBuilderMesh mesh)
|
||||
{
|
||||
mesh.Optimize();
|
||||
}
|
||||
|
||||
static void OnAfterAssemblyReload()
|
||||
{
|
||||
// The inspector icon for ProBuilderMesh is set in the component metadata. However, this also serves as the
|
||||
// scene view gizmo icon, which we do not want. To avoid drawing an icon for every mesh in the Scene View,
|
||||
// we simply tell the AnnotationManager to not render the icon. This _does_ put ProBuilderMesh in the
|
||||
// "Recently Changed" list, but only when it is modified the first time.
|
||||
// The alternative method of setting an icon is to place it in a folder named "Editor Default Resources/Icons",
|
||||
// however that requires that the resources directory be in "Assets", which we do not want to do.
|
||||
EditorApplication.delayCall += () => EditorUtility.SetGizmoIconEnabled(typeof(ProBuilderMesh), false);
|
||||
}
|
||||
|
||||
static void PrefabInstanceReverting(GameObject go)
|
||||
{
|
||||
// revert will leak meshes unless we clean up before ProBuilderMesh.m_Mesh is set to null
|
||||
foreach (var mesh in go.GetComponentsInChildren<ProBuilderMesh>())
|
||||
mesh.DestroyUnityMesh();
|
||||
}
|
||||
|
||||
static void PrefabInstanceUpdated(GameObject go)
|
||||
{
|
||||
if (EditorApplication.isPlayingOrWillChangePlaymode)
|
||||
return;
|
||||
|
||||
foreach (var mesh in go.GetComponentsInChildren<ProBuilderMesh>())
|
||||
{
|
||||
EditorUtility.SynchronizeWithMeshFilter(mesh);
|
||||
mesh.Rebuild();
|
||||
mesh.Optimize();
|
||||
}
|
||||
|
||||
ProBuilderEditor.Refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4897f4be94b09d4a90eb6a4d9b85c55
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
interface IHasPreferences
|
||||
{
|
||||
void ReloadPreferences();
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 09802f6381a8fb2478f18a624b459ff8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,51 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes icon styles for pro and basic skin, or default (whatever the editor is currently using).
|
||||
/// </summary>
|
||||
enum IconSkin
|
||||
{
|
||||
Default,
|
||||
Light,
|
||||
Pro
|
||||
};
|
||||
|
||||
static class IconUtility
|
||||
{
|
||||
static Dictionary<string, Texture2D> s_Icons = new Dictionary<string, Texture2D>();
|
||||
|
||||
static string s_IconFolderPath = "Packages/com.unity.probuilder/Editor Default Resources/Icons/";
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_Icons.Clear();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load an icon from icons folder located in the package's 'Editor Default Resources'.
|
||||
/// Naming convention is: "path/to/iconName" (without extension). Use the 'd_' prefix for dark skin icons.
|
||||
/// No prefix or suffit for light skin icons. This method assumes the file is .png.
|
||||
/// </summary>
|
||||
/// <param name="iconName">Relative path to the icon without the file extension in the filename.</param>
|
||||
/// <returns>A valid icon texture. Otherwise null is returned.</returns>
|
||||
public static Texture2D GetIcon(string iconName)
|
||||
{
|
||||
Texture2D icon = null;
|
||||
|
||||
if (!s_Icons.TryGetValue(iconName, out icon))
|
||||
{
|
||||
string fullPath = Path.Combine(s_IconFolderPath, iconName + (Path.HasExtension(iconName)? string.Empty: ".png"));
|
||||
|
||||
icon = EditorGUIUtility.LoadIcon(fullPath);
|
||||
if(icon != null)
|
||||
s_Icons.Add(iconName, icon);
|
||||
}
|
||||
return icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3a9ebc9851a2d8e4c868fa97dbbb35f6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,43 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
sealed class LicenseEditor : EditorWindow
|
||||
{
|
||||
const string k_LicenseTextPath = "Third Party Notices.md";
|
||||
const int k_MaxStringLength = 60000;
|
||||
TextAsset m_LicenseText;
|
||||
string[] m_LicenseContents;
|
||||
Vector2 m_Scroll;
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_LicenseText = FileUtility.LoadInternalAsset<TextAsset>(k_LicenseTextPath);
|
||||
int charCount = m_LicenseText.text.Length;
|
||||
int stringCount = charCount / k_MaxStringLength + 1;
|
||||
m_LicenseContents = new string[stringCount];
|
||||
int index = 0;
|
||||
for (int i = 0; i < stringCount; i++)
|
||||
{
|
||||
int length = Math.Min(k_MaxStringLength, charCount - index);
|
||||
m_LicenseContents[i] = m_LicenseText.text.Substring(index, length);
|
||||
index += length;
|
||||
}
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (m_LicenseContents == null)
|
||||
return;
|
||||
|
||||
m_Scroll = EditorGUILayout.BeginScrollView(m_Scroll);
|
||||
|
||||
for (int i = 0, c = m_LicenseContents.Length; i < c; i++)
|
||||
GUILayout.Label(m_LicenseContents[i], EditorStyles.wordWrappedLabel);
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 563aeff009644264e815e6de1439e784
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,84 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
sealed class LightmapUVEditor : EditorWindow
|
||||
{
|
||||
List<ProBuilderMesh> m_MissingLightmaps = new List<ProBuilderMesh>();
|
||||
|
||||
GUIContent m_AutoLightmapUVContent = new GUIContent("Auto Lightmap UVs", "Automatically build the lightmap UV array when editing ProBuilder meshes. If this feature is disabled, you will need to use the 'Generate UV2' action to build lightmap UVs for meshes prior to baking lightmaps.");
|
||||
internal static readonly Rect desiredPosition = new Rect(100, 100, 348, 234);
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
m_MissingLightmaps = FindMissingLightmaps();
|
||||
EditorMeshUtility.meshOptimized += MeshOptimized;
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
GUILayout.Label("Lightmap UV Settings", EditorStyles.boldLabel);
|
||||
|
||||
Lightmapping.autoUnwrapLightmapUV = EditorGUILayout.Toggle(m_AutoLightmapUVContent, Lightmapping.autoUnwrapLightmapUV);
|
||||
|
||||
if (m_MissingLightmaps.Count > 0)
|
||||
{
|
||||
EditorGUILayout.HelpBox(GetMissingLightmapText(), MessageType.Warning);
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
|
||||
if (GUILayout.Button("Build Missing Lightmap UVs"))
|
||||
{
|
||||
// copy the missing lightmaps array so that MeshOptimized does not interfere with the rebuild iterator
|
||||
Lightmapping.RebuildMissingLightmapUVs(m_MissingLightmaps.ToArray());
|
||||
EditorUtility.ShowNotification("Rebuild Missing Lightmap UVs");
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
void MeshOptimized(ProBuilderMesh mesh, Mesh umesh)
|
||||
{
|
||||
var missing = IsMissingLightmaps(mesh);
|
||||
|
||||
if (missing)
|
||||
{
|
||||
if (!m_MissingLightmaps.Contains(mesh))
|
||||
m_MissingLightmaps.Add(mesh);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (m_MissingLightmaps.Contains(mesh))
|
||||
m_MissingLightmaps.Remove(mesh);
|
||||
}
|
||||
|
||||
Repaint();
|
||||
}
|
||||
|
||||
string GetMissingLightmapText()
|
||||
{
|
||||
var count = m_MissingLightmaps.Count;
|
||||
|
||||
if (count < 2)
|
||||
return "There is 1 mesh missing Lightmap UVs in the open scenes.";
|
||||
|
||||
return "There are " + m_MissingLightmaps.Count + " meshes missing Lightmap UVs in the open scenes.";
|
||||
}
|
||||
|
||||
static bool IsMissingLightmaps(ProBuilderMesh mesh)
|
||||
{
|
||||
return mesh.gameObject.HasStaticFlag(StaticEditorFlags.ContributeGI) && !mesh.HasArrays(MeshArrays.Lightmap);
|
||||
}
|
||||
|
||||
static List<ProBuilderMesh> FindMissingLightmaps()
|
||||
{
|
||||
return EditorUtility.FindObjectsByType<ProBuilderMesh>().Where(IsMissingLightmaps).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b38e0917ff1e4a54292a5137af265542
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,262 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UL = UnityEditor.Lightmapping;
|
||||
using UnityEditor.SettingsManagement;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Methods used in manipulating or creating Lightmaps.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
static class Lightmapping
|
||||
{
|
||||
const StaticEditorFlags k_ContributeGI = StaticEditorFlags.ContributeGI;
|
||||
|
||||
const string k_StaticEditorFlagsProperty = "m_StaticEditorFlags";
|
||||
|
||||
[UserSetting("General", "Auto Lightmap UVs", "Automatically build the lightmap UV array when editing ProBuilder meshes. If this feature is disabled, you will need to use the 'Generate UV2' action to build lightmap UVs for meshes prior to baking lightmaps.")]
|
||||
static Pref<bool> s_AutoUnwrapLightmapUV = new Pref<bool>("lightmapping.autoUnwrapLightmapUV", true);
|
||||
|
||||
[UserSetting("General", "Show Missing Lightmap UVs Warning", "Enable or disable a warning log if lightmaps are baked while ProBuilder shapes are missing a valid UV2 channel.")]
|
||||
static Pref<bool> s_ShowMissingLightmapUVWarning = new Pref<bool>("lightmapping.showMissingLightmapWarning", true, SettingsScope.User);
|
||||
|
||||
[UserSetting]
|
||||
internal static Pref<UnwrapParameters> s_UnwrapParameters = new Pref<UnwrapParameters>("lightmapping.defaultLightmapUnwrapParameters", new UnwrapParameters());
|
||||
#pragma warning disable 618
|
||||
static Pref<UL.GIWorkflowMode> s_GiWorkflowMode = new Pref<UL.GIWorkflowMode>("lightmapping.giWorkflowMode", UL.GIWorkflowMode.Iterative, SettingsScope.User);
|
||||
#pragma warning restore 618
|
||||
static class Styles
|
||||
{
|
||||
public static readonly GUIContent hardAngle = new GUIContent("Hard Angle", "Angle between neighbor triangles that will generate seam.");
|
||||
public static readonly GUIContent packMargin = new GUIContent("Pack Margin", "Measured in pixels, assuming mesh will cover an entire 1024x1024 lightmap.");
|
||||
public static readonly GUIContent angleError = new GUIContent("Angle Error", "Measured in percents. Angle error measures deviation of UV angles from geometry angles.");
|
||||
public static readonly GUIContent areaError = new GUIContent("Area Error", "");
|
||||
|
||||
static bool s_Initialized;
|
||||
public static GUIStyle s_MiniButton;
|
||||
public static GUIStyle miniButton { get { Init(); return s_MiniButton; } }
|
||||
public static bool unwrapSettingsFoldout;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
if (s_Initialized)
|
||||
return;
|
||||
|
||||
s_Initialized = true;
|
||||
|
||||
s_MiniButton = new GUIStyle(GUI.skin.button);
|
||||
s_MiniButton.stretchHeight = false;
|
||||
s_MiniButton.stretchWidth = false;
|
||||
s_MiniButton.padding = new RectOffset(6, 6, 3, 3);
|
||||
s_MiniButton.margin = new RectOffset(4, 4, 4, 0);
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
internal static void Reset()
|
||||
{
|
||||
s_Initialized = false;
|
||||
unwrapSettingsFoldout = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
[UserSettingBlock("Mesh Settings")]
|
||||
static void UnwrapSettingDefaults(string searchContext)
|
||||
{
|
||||
Styles.Init();
|
||||
var isSearching = !string.IsNullOrEmpty(searchContext);
|
||||
|
||||
if (!isSearching)
|
||||
Styles.unwrapSettingsFoldout = EditorGUILayout.Foldout(Styles.unwrapSettingsFoldout, "Lightmap UVs Settings");
|
||||
|
||||
if (isSearching || Styles.unwrapSettingsFoldout)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
var unwrap = (UnwrapParameters)s_UnwrapParameters;
|
||||
|
||||
using (new SettingsGUILayout.IndentedGroup())
|
||||
{
|
||||
unwrap.hardAngle = SettingsGUILayout.SearchableSlider(Styles.hardAngle, unwrap.hardAngle, 1f, 180f, searchContext);
|
||||
unwrap.packMargin = SettingsGUILayout.SearchableSlider(Styles.packMargin, unwrap.packMargin, 1f, 64f, searchContext);
|
||||
unwrap.angleError = SettingsGUILayout.SearchableSlider(Styles.angleError, unwrap.angleError, 1f, 75f, searchContext);
|
||||
unwrap.areaError = SettingsGUILayout.SearchableSlider(Styles.areaError, unwrap.areaError, 1f, 75f, searchContext);
|
||||
|
||||
if (!isSearching)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Reset", Styles.miniButton))
|
||||
unwrap.Reset();
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
|
||||
SettingsGUILayout.DoResetContextMenuForLastRect(s_UnwrapParameters);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
s_UnwrapParameters.value = unwrap;
|
||||
}
|
||||
}
|
||||
|
||||
public static bool autoUnwrapLightmapUV
|
||||
{
|
||||
get { return (bool)s_AutoUnwrapLightmapUV; }
|
||||
set { s_AutoUnwrapLightmapUV.value = value; }
|
||||
}
|
||||
|
||||
static Lightmapping()
|
||||
{
|
||||
UL.bakeCompleted += OnLightmappingCompleted;
|
||||
Undo.postprocessModifications += PostprocessModifications;
|
||||
}
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
Styles.Reset();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the LightmapStatic bit of an objects Static flags.
|
||||
/// </summary>
|
||||
/// <param name="pb"></param>
|
||||
/// <param name="isEnabled"></param>
|
||||
public static void SetLightmapStaticFlagEnabled(ProBuilderMesh pb, bool isEnabled)
|
||||
{
|
||||
Entity ent = pb.GetComponent<Entity>();
|
||||
|
||||
if (ent != null && ent.entityType == EntityType.Detail)
|
||||
{
|
||||
StaticEditorFlags flags = GameObjectUtility.GetStaticEditorFlags(pb.gameObject);
|
||||
|
||||
if (isEnabled != (flags & k_ContributeGI) > 0)
|
||||
{
|
||||
flags ^= k_ContributeGI;
|
||||
GameObjectUtility.SetStaticEditorFlags(pb.gameObject, flags);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static UndoPropertyModification[] PostprocessModifications(UndoPropertyModification[] modifications)
|
||||
{
|
||||
if (!autoUnwrapLightmapUV)
|
||||
return modifications;
|
||||
|
||||
foreach (var modification in modifications)
|
||||
{
|
||||
string property = modification.currentValue == null ? null : modification.currentValue.propertyPath;
|
||||
|
||||
if (string.IsNullOrEmpty(property)
|
||||
|| !property.Equals(k_StaticEditorFlagsProperty)
|
||||
|| string.IsNullOrEmpty(modification.currentValue.value))
|
||||
continue;
|
||||
|
||||
var staticFlags = uint.Parse(modification.currentValue.value);
|
||||
var lightmapStatic = (staticFlags & (uint) k_ContributeGI) != 0;
|
||||
|
||||
if (lightmapStatic)
|
||||
{
|
||||
var gameObject = modification.currentValue.target as GameObject;
|
||||
|
||||
if (gameObject != null && gameObject.TryGetComponent<ProBuilderMesh>(out var mesh))
|
||||
{
|
||||
mesh.Optimize();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return modifications;
|
||||
}
|
||||
|
||||
static void OnLightmappingCompleted()
|
||||
{
|
||||
if (!s_ShowMissingLightmapUVWarning)
|
||||
return;
|
||||
|
||||
var missingUv2 = EditorUtility.FindObjectsByType<ProBuilderMesh>().Where(x => !x.HasArrays(MeshArrays.Lightmap) && x.gameObject.HasStaticFlag(k_ContributeGI));
|
||||
|
||||
int count = missingUv2.Count();
|
||||
|
||||
if (count > 0)
|
||||
Log.Warning("{0} ProBuilder {1} included in lightmap bake with missing UV2. " +
|
||||
"Use \"Tools > ProBuilder > Editors > Open Lightmap UV Editor\" to find and generate missing UVs." +
|
||||
"\nYou can turn off this warning in Preferences/ProBuilder.",
|
||||
count, count == 1 ? "mesh" : "meshes");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build Lightmap UVs for each mesh in the selection that is missing the UV2 array.
|
||||
/// </summary>
|
||||
/// <param name="selection"></param>
|
||||
/// <param name="showProgressBar"></param>
|
||||
public static int RebuildMissingLightmapUVs(IEnumerable<ProBuilderMesh> selection, bool showProgressBar = false)
|
||||
{
|
||||
int count = 0;
|
||||
float total = selection.Count(x => x.gameObject.HasStaticFlag(k_ContributeGI) && !x.HasArrays(MeshArrays.Lightmap));
|
||||
|
||||
foreach (var mesh in selection)
|
||||
{
|
||||
if (!mesh.gameObject.HasStaticFlag(k_ContributeGI) || mesh.HasArrays(MeshArrays.Texture1))
|
||||
continue;
|
||||
|
||||
if (showProgressBar)
|
||||
{
|
||||
if (UnityEditor.EditorUtility.DisplayCancelableProgressBar("Generate Lightmap UVs", "Unwrapping UVs for mesh: " + mesh.name, count / total))
|
||||
break;
|
||||
}
|
||||
|
||||
count++;
|
||||
mesh.Optimize(true);
|
||||
}
|
||||
|
||||
UnityEditor.EditorUtility.ClearProgressBar();
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the UnwrapParam values from a pb_UnwrapParameters object.
|
||||
* Not in pb_UnwrapParameters because UnwrapParam is an Editor class.
|
||||
*/
|
||||
public static UnwrapParam GetUnwrapParam(UnwrapParameters parameters)
|
||||
{
|
||||
UnwrapParam param = new UnwrapParam();
|
||||
|
||||
if (parameters != null)
|
||||
{
|
||||
param.angleError = Mathf.Clamp(parameters.angleError, 1f, 75f) * .01f;
|
||||
param.areaError = Mathf.Clamp(parameters.areaError , 1f, 75f) * .01f;
|
||||
param.hardAngle = Mathf.Clamp(parameters.hardAngle , 0f, 180f);
|
||||
param.packMargin = Mathf.Clamp(parameters.packMargin, 1f, 64) * .001f;
|
||||
}
|
||||
else
|
||||
{
|
||||
param.angleError = Mathf.Clamp(UnwrapParameters.k_AngleError, 1f, 75f) * .01f;
|
||||
param.areaError = Mathf.Clamp(UnwrapParameters.k_AreaError , 1f, 75f) * .01f;
|
||||
param.hardAngle = Mathf.Clamp(UnwrapParameters.k_HardAngle , 0f, 180f);
|
||||
param.packMargin = Mathf.Clamp(UnwrapParameters.k_PackMargin, 1f, 64) * .001f;
|
||||
}
|
||||
|
||||
return param;
|
||||
}
|
||||
#pragma warning disable 618
|
||||
internal static void PushGIWorkflowMode()
|
||||
{
|
||||
s_GiWorkflowMode.SetValue(UL.giWorkflowMode, true);
|
||||
|
||||
if (UL.giWorkflowMode != UL.GIWorkflowMode.Legacy)
|
||||
UL.giWorkflowMode = UL.GIWorkflowMode.OnDemand;
|
||||
}
|
||||
|
||||
internal static void PopGIWorkflowMode()
|
||||
{
|
||||
UL.giWorkflowMode = s_GiWorkflowMode;
|
||||
}
|
||||
#pragma warning restore 618
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a0ae93cadae49f8488c27d0d40da0e99
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEditor.ProBuilder.UI;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Initialize Log preferences here because Log does not have access to preference values.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
static class LogPreferences
|
||||
{
|
||||
static LogPreferences()
|
||||
{
|
||||
SetLogPreferences();
|
||||
}
|
||||
|
||||
internal static void SetLogPreferences()
|
||||
{
|
||||
EditorApplication.delayCall += () =>
|
||||
{
|
||||
Log.SetLogLevel(LogEditor.s_LogLevel);
|
||||
Log.SetOutput(LogEditor.s_LogOutput);
|
||||
Log.SetLogFile(LogEditor.s_LogPath);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Manage settings for pb_Log.
|
||||
/// </summary>
|
||||
sealed class LogEditor : EditorWindow
|
||||
{
|
||||
internal static Pref<LogLevel> s_LogLevel = new Pref<LogLevel>("log.level", LogLevel.Default);
|
||||
internal static Pref<LogOutput> s_LogOutput = new Pref<LogOutput>("log.output", LogOutput.Console);
|
||||
internal static Pref<string> s_LogPath = new Pref<string>("log.path", Log.k_ProBuilderLogFileName);
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Debug/Log Preferences")]
|
||||
static void MenuInit()
|
||||
{
|
||||
GetWindow<LogEditor>(true, "Log Preferences", true);
|
||||
}
|
||||
|
||||
static GUIContent[] gc_output = new GUIContent[]
|
||||
{
|
||||
new GUIContent("Console"),
|
||||
new GUIContent("File")
|
||||
};
|
||||
|
||||
static GUIContent[] gc_level = new GUIContent[]
|
||||
{
|
||||
new GUIContent("Error"),
|
||||
new GUIContent("Warning"),
|
||||
new GUIContent("Info"),
|
||||
new GUIContent("Debug"),
|
||||
new GUIContent("All")
|
||||
};
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
GUILayout.Label("Log Output", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
s_LogOutput.value = (LogOutput)global::UnityEditor.ProBuilder.UI.EditorGUILayout.FlagToolbar((int)s_LogOutput.value, gc_output);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
ProBuilderSettings.Save();
|
||||
LogPreferences.SetLogPreferences();
|
||||
}
|
||||
|
||||
GUI.enabled = (s_LogOutput & LogOutput.File) > 0;
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Label("Log Path");
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("...", EditorStyles.miniButton))
|
||||
{
|
||||
string dir = UnityEditor.EditorUtility.OpenFolderPanel("ProBuilder Log Directory", "", "");
|
||||
|
||||
if (!string.IsNullOrEmpty(dir) && Directory.Exists(dir))
|
||||
{
|
||||
s_LogPath.value = string.Format("{0}/{1}", dir, Log.k_ProBuilderLogFileName);
|
||||
|
||||
try
|
||||
{
|
||||
Uri directoryUri = new Uri(dir);
|
||||
Uri fileUri = new Uri(s_LogPath);
|
||||
string relativePath = directoryUri.MakeRelativeUri(fileUri).ToString();
|
||||
if (!string.IsNullOrEmpty(relativePath))
|
||||
s_LogPath.value = relativePath;
|
||||
}
|
||||
catch {}
|
||||
|
||||
ProBuilderSettings.Save();
|
||||
Log.SetLogFile(s_LogPath);
|
||||
}
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
GUILayout.BeginHorizontal();
|
||||
GUI.enabled = false;
|
||||
EditorGUILayout.LabelField(s_LogPath);
|
||||
GUI.enabled = true;
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Open", EditorStyles.miniButton))
|
||||
UnityEditor.EditorUtility.OpenWithDefaultApp(s_LogPath);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Label("Chatty-ness", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
s_LogLevel.value = (LogLevel)global::UnityEditor.ProBuilder.UI.EditorGUILayout.FlagToolbar((int)s_LogLevel.value, gc_level, false, true);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
ProBuilderSettings.Save();
|
||||
Log.SetLogLevel(s_LogLevel);
|
||||
}
|
||||
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.FlexibleSpace();
|
||||
if (GUILayout.Button("Clear Log File", EditorStyles.miniButton))
|
||||
Log.ClearLogFile();
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb7d601e6d679aa4dbd856d20dda3f74
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,426 @@
|
||||
#pragma warning disable 0618
|
||||
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.ShortcutManagement;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.ProBuilder.MeshOperations;
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Assign materials to faces and objects.
|
||||
/// </summary>
|
||||
sealed class MaterialEditor : ConfigurableWindow
|
||||
{
|
||||
class MaterialShortcutContext : IShortcutContext
|
||||
{
|
||||
public bool active
|
||||
=> EditorWindow.focusedWindow is SceneView
|
||||
&& instance != null && MeshSelection.selectedObjectCount > 0
|
||||
&& instance.m_QueuedMaterial.value != null;
|
||||
}
|
||||
|
||||
// Reference to pb_Editor instance.
|
||||
static ProBuilderEditor editor { get { return ProBuilderEditor.instance; } }
|
||||
|
||||
// Reference to the currently open pb_Material_Editor
|
||||
public static MaterialEditor instance { get; private set; }
|
||||
|
||||
const string k_QuickMaterialPath = "Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Quick Material";
|
||||
|
||||
[MenuItem(k_QuickMaterialPath, true, PreferenceKeys.menuMaterialColors)]
|
||||
public static bool VerifyQuickMaterialAction()
|
||||
{
|
||||
return ProBuilderEditor.instance != null && MeshSelection.selectedObjectCount > 0 && instance != null && instance.m_QueuedMaterial.value != null;
|
||||
}
|
||||
|
||||
[MenuItem(k_QuickMaterialPath, false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyQuickMaterial()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, instance.m_QueuedMaterial.value);
|
||||
}
|
||||
|
||||
[Shortcut("ProBuilder/Apply Quick Material", typeof(MaterialShortcutContext), KeyCode.Mouse2, defaultShortcutModifiers: ShortcutModifiers.Shift | ShortcutModifiers.Control)]
|
||||
public static void ApplyQuickMaterialShortcut()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, instance.m_QueuedMaterial.value);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 1", true, PreferenceKeys.menuMaterialColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 2", true, PreferenceKeys.menuMaterialColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 3", true, PreferenceKeys.menuMaterialColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 4", true, PreferenceKeys.menuMaterialColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 5", true, PreferenceKeys.menuMaterialColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 6", true, PreferenceKeys.menuMaterialColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 7", true, PreferenceKeys.menuMaterialColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 8", true, PreferenceKeys.menuMaterialColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 9", true, PreferenceKeys.menuMaterialColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 10", true, PreferenceKeys.menuMaterialColors)]
|
||||
public static bool VerifyMaterialAction()
|
||||
{
|
||||
return ProBuilderEditor.instance != null && MeshSelection.selectedObjectCount > 0;
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 1", false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyMaterial0()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, CurrentPalette[0]);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 2", false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyMaterial1()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, CurrentPalette[1]);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 3", false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyMaterial2()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, CurrentPalette[2]);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 4", false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyMaterial3()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, CurrentPalette[3]);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 5", false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyMaterial4()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, CurrentPalette[4]);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 6", false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyMaterial5()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, CurrentPalette[5]);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 7", false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyMaterial6()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, CurrentPalette[6]);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 8", false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyMaterial7()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, CurrentPalette[7]);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 9", false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyMaterial8()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, CurrentPalette[8]);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset 10", false, PreferenceKeys.menuMaterialColors)]
|
||||
public static void ApplyMaterial9()
|
||||
{
|
||||
ApplyMaterial(MeshSelection.topInternal, CurrentPalette[9]);
|
||||
}
|
||||
|
||||
// Path to the required default material palette. If not valid material palettes are
|
||||
// found a new one will be created with this path (relative to ProBuilder folder).
|
||||
static Pref<string> s_MaterialPalettePath = new Pref<string>("editor.materialPalettePath", FileUtility.GetLocalDataDirectory() + "/Default Material Palette.asset");
|
||||
|
||||
// The currently loaded material palette asset.
|
||||
static MaterialPalette s_CurrentPalette = null;
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_CurrentPalette = null;
|
||||
instance?.RefreshAvailablePalettes();
|
||||
}
|
||||
|
||||
// The user set "quick material"
|
||||
Pref<Material> m_QueuedMaterial = new Pref<Material>("materialEditor.quickMaterial", null, SettingsScope.User);
|
||||
|
||||
// Custom style for material row background
|
||||
GUIStyle m_RowBackgroundStyle;
|
||||
|
||||
// The view scroll position.
|
||||
Vector2 m_ViewScroll = Vector2.zero;
|
||||
|
||||
// All available material palettes
|
||||
MaterialPalette[] m_AvailablePalettes = null;
|
||||
|
||||
// List of string names for all available palettes (plus one entry for 'Add New')
|
||||
string[] m_AvailablePalettes_Str = null;
|
||||
|
||||
// The index of the currently loaded material palette in m_AvailablePalettes
|
||||
int m_CurrentPaletteIndex = 0;
|
||||
|
||||
MaterialShortcutContext m_ShortcutContext;
|
||||
|
||||
/// <summary>
|
||||
/// The currently loaded material palette, or a default.
|
||||
/// </summary>
|
||||
public static MaterialPalette CurrentPalette
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_CurrentPalette == null)
|
||||
{
|
||||
// Attempt to load the last user-set material palette
|
||||
s_CurrentPalette = AssetDatabase.LoadAssetAtPath<MaterialPalette>(s_MaterialPalettePath);
|
||||
|
||||
// If not set (or deleted), fall back on default
|
||||
if (s_CurrentPalette != null)
|
||||
return s_CurrentPalette;
|
||||
|
||||
// No dice - iterate any other pb_MaterialPalette objects in the project (favoring first found)
|
||||
s_CurrentPalette = FileUtility.FindAssetOfType<MaterialPalette>();
|
||||
|
||||
if (s_CurrentPalette != null)
|
||||
return s_CurrentPalette;
|
||||
|
||||
// If no existing pb_MaterialPalette objects in project create a new one
|
||||
s_CurrentPalette = FileUtility.LoadRequired<MaterialPalette>(s_MaterialPalettePath);
|
||||
}
|
||||
return s_CurrentPalette;
|
||||
}
|
||||
}
|
||||
|
||||
public static void MenuOpenMaterialEditor()
|
||||
{
|
||||
GetWindow<MaterialEditor>("Material Editor");
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
instance = this;
|
||||
this.autoRepaintOnSceneChange = true;
|
||||
this.minSize = new Vector2(236, 250);
|
||||
m_RowBackgroundStyle = new GUIStyle();
|
||||
m_RowBackgroundStyle.normal.background = EditorGUIUtility.whiteTexture;
|
||||
s_CurrentPalette = null;
|
||||
RefreshAvailablePalettes();
|
||||
|
||||
ShortcutManager.RegisterContext(m_ShortcutContext ??= new MaterialShortcutContext());
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
ShortcutManager.UnregisterContext(m_ShortcutContext);
|
||||
instance = null;
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
DoContextMenu();
|
||||
|
||||
GUILayout.Label("Quick Material", EditorStyles.boldLabel);
|
||||
Rect r = GUILayoutUtility.GetLastRect();
|
||||
int left = (int)position.width - 68;
|
||||
|
||||
GUILayout.BeginHorizontal(GUILayout.MaxWidth(position.width - 74), GUILayout.MinHeight(64));
|
||||
GUILayout.BeginVertical();
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
m_QueuedMaterial.value = (Material)EditorGUILayout.ObjectField(m_QueuedMaterial.value, typeof(Material), true);
|
||||
|
||||
GUILayout.Space(2);
|
||||
|
||||
GUI.enabled = editor != null && MeshSelection.selectedFaceCount > 0;
|
||||
if (GUILayout.Button("Match Selection", GUILayout.MaxWidth(120)))
|
||||
{
|
||||
m_QueuedMaterial.SetValue(EditorMaterialUtility.GetActiveSelection());
|
||||
}
|
||||
GUI.enabled = true;
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
var quickMaterialShortcut = string.Empty;
|
||||
try
|
||||
{
|
||||
quickMaterialShortcut = ShortcutManager.instance.GetShortcutBinding("Main Menu/" + k_QuickMaterialPath).ToString();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
var quickMaterialButtonLabel = string.IsNullOrEmpty(quickMaterialShortcut) ? "Apply" : $"Apply ({quickMaterialShortcut})";
|
||||
if (GUILayout.Button(quickMaterialButtonLabel))
|
||||
ApplyMaterial(MeshSelection.topInternal, m_QueuedMaterial.value);
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUI.Box(new Rect(left, r.y + r.height + 2, 64, 64), "");
|
||||
|
||||
var previewTexture = EditorMaterialUtility.GetPreviewTexture(m_QueuedMaterial.value);
|
||||
|
||||
if (previewTexture != null)
|
||||
{
|
||||
GUI.Label(new Rect(left + 2, r.y + r.height + 4, 60, 60), previewTexture);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.Box(new Rect(left + 2, r.y + r.height + 4, 60, 60), "");
|
||||
GUI.Label(new Rect(left + 2, r.height + 28, 120, 32), "None\n(Texture)");
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUILayout.Space(4);
|
||||
|
||||
GUI.backgroundColor = PreferenceKeys.proBuilderDarkGray;
|
||||
UI.EditorGUIUtility.DrawSeparator(2);
|
||||
GUI.backgroundColor = Color.white;
|
||||
|
||||
GUILayout.Label("Material Palette", EditorStyles.boldLabel);
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
m_CurrentPaletteIndex = EditorGUILayout.Popup("", m_CurrentPaletteIndex, m_AvailablePalettes_Str);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
{
|
||||
MaterialPalette newPalette = null;
|
||||
|
||||
// Add a new material palette
|
||||
if (m_CurrentPaletteIndex >= m_AvailablePalettes.Length)
|
||||
{
|
||||
string path = AssetDatabase.GenerateUniqueAssetPath("Assets/Material Palette.asset");
|
||||
newPalette = FileUtility.LoadRequired<MaterialPalette>(path);
|
||||
EditorGUIUtility.PingObject(newPalette);
|
||||
}
|
||||
else
|
||||
{
|
||||
newPalette = m_AvailablePalettes[m_CurrentPaletteIndex];
|
||||
}
|
||||
|
||||
SetMaterialPalette(newPalette);
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
s_CurrentPalette = (MaterialPalette)EditorGUILayout.ObjectField(s_CurrentPalette, typeof(MaterialPalette), false);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SetMaterialPalette(s_CurrentPalette);
|
||||
|
||||
GUILayout.Space(4);
|
||||
|
||||
Material[] materials = CurrentPalette;
|
||||
|
||||
m_ViewScroll = GUILayout.BeginScrollView(m_ViewScroll);
|
||||
|
||||
for (int i = 0; i < materials.Length; i++)
|
||||
{
|
||||
if (i == 10)
|
||||
{
|
||||
GUILayout.Space(2);
|
||||
GUI.backgroundColor = PreferenceKeys.proBuilderLightGray;
|
||||
UI.EditorGUIUtility.DrawSeparator(1);
|
||||
GUI.backgroundColor = Color.white;
|
||||
GUILayout.Space(2);
|
||||
}
|
||||
|
||||
GUILayout.BeginHorizontal();
|
||||
if (i < 10)
|
||||
{
|
||||
var applyMaterialPresetShortcut = string.Empty;
|
||||
try
|
||||
{
|
||||
var shortcutPath = "Main Menu/Tools/" + PreferenceKeys.pluginTitle + "/Materials/Apply Material Preset " + (i + 1);
|
||||
applyMaterialPresetShortcut = ShortcutManager.instance.GetShortcutBinding(shortcutPath).ToString();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
var applyMaterialPresetButtonLabel = string.IsNullOrEmpty(applyMaterialPresetShortcut)
|
||||
? $"Apply {i}"
|
||||
: $"Apply {i} ({applyMaterialPresetShortcut})";
|
||||
if (GUILayout.Button(applyMaterialPresetButtonLabel, EditorStyles.miniButton, GUILayout.MinWidth(50), GUILayout.MaxWidth(150)))
|
||||
ApplyMaterial(MeshSelection.topInternal, materials[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GUILayout.Button("Apply", EditorStyles.miniButtonLeft, GUILayout.MaxWidth(44)))
|
||||
ApplyMaterial(MeshSelection.topInternal, materials[i]);
|
||||
|
||||
GUI.backgroundColor = Color.red;
|
||||
if (GUILayout.Button("", EditorStyles.miniButtonRight, GUILayout.MaxWidth(14)))
|
||||
{
|
||||
Material[] temp = new Material[materials.Length - 1];
|
||||
System.Array.Copy(materials, 0, temp, 0, materials.Length - 1);
|
||||
materials = temp;
|
||||
SaveUserMaterials(materials);
|
||||
|
||||
GUIUtility.ExitGUI();
|
||||
return;
|
||||
}
|
||||
GUI.backgroundColor = Color.white;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
materials[i] = (Material)EditorGUILayout.ObjectField(materials[i], typeof(Material), false);
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
SaveUserMaterials(materials);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
|
||||
if (GUILayout.Button("Add"))
|
||||
{
|
||||
Material[] temp = new Material[materials.Length + 1];
|
||||
System.Array.Copy(materials, 0, temp, 0, materials.Length);
|
||||
materials = temp;
|
||||
SaveUserMaterials(materials);
|
||||
}
|
||||
|
||||
GUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
static void ApplyMaterial(IEnumerable<ProBuilderMesh> selection, Material mat)
|
||||
{
|
||||
if (mat == null)
|
||||
return;
|
||||
|
||||
UndoUtility.RecordComponents<MeshRenderer, ProBuilderMesh>(selection, "Set Material");
|
||||
|
||||
foreach (var mesh in selection)
|
||||
{
|
||||
var applyPerFace = ProBuilderEditor.instance != null && ProBuilderEditor.selectMode.ContainsFlag(SelectMode.Face) && mesh.faceCount > 0;
|
||||
mesh.SetMaterial(applyPerFace ? mesh.GetSelectedFaces() : mesh.facesInternal, mat);
|
||||
InternalMeshUtility.FilterUnusedSubmeshIndexes(mesh);
|
||||
mesh.Rebuild();
|
||||
mesh.Optimize();
|
||||
}
|
||||
|
||||
if (ProBuilderEditor.instance != null && MeshSelection.selectedFaceCount > 0)
|
||||
EditorUtility.ShowNotification("Set Material\n" + mat.name);
|
||||
}
|
||||
|
||||
static void SaveUserMaterials(Material[] materials)
|
||||
{
|
||||
s_CurrentPalette.array = materials;
|
||||
UnityEditor.EditorUtility.SetDirty(s_CurrentPalette);
|
||||
AssetDatabase.SaveAssets();
|
||||
}
|
||||
|
||||
void SetMaterialPalette(MaterialPalette palette)
|
||||
{
|
||||
s_CurrentPalette = palette;
|
||||
RefreshAvailablePalettes();
|
||||
}
|
||||
|
||||
void RefreshAvailablePalettes()
|
||||
{
|
||||
MaterialPalette cur = CurrentPalette;
|
||||
m_AvailablePalettes = FileUtility.FindAndLoadAssets<MaterialPalette>();
|
||||
m_AvailablePalettes_Str = m_AvailablePalettes.Select(x => x.name).ToArray();
|
||||
ArrayUtility.Add<string>(ref m_AvailablePalettes_Str, string.Empty);
|
||||
ArrayUtility.Add<string>(ref m_AvailablePalettes_Str, "New Material Palette...");
|
||||
m_CurrentPaletteIndex = System.Array.IndexOf(m_AvailablePalettes, cur);
|
||||
s_MaterialPalettePath.SetValue(AssetDatabase.GetAssetPath(cur), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9be09300178708d4aaa608eed9697f1c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,70 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Linq;
|
||||
using System.Collections;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// A serializable object that stores an array of materials. Used by MaterialEditor.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="UnityEngine.ScriptableObject"/>
|
||||
/// <inheritdoc cref="IHasDefault"/>
|
||||
sealed class MaterialPalette : ScriptableObject, IHasDefault
|
||||
{
|
||||
[MenuItem("Assets/Create/Material Palette", true)]
|
||||
static bool VerifyCreateMaterialPalette()
|
||||
{
|
||||
// This hangs on large projects
|
||||
// Selection.GetFiltered(typeof(Material), SelectionMode.DeepAssets).Length > 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Create/Material Palette")]
|
||||
static void CreateMaterialPalette()
|
||||
{
|
||||
string path = FileUtility.GetSelectedDirectory() + "/Material Palette.asset";
|
||||
|
||||
// Only generate unique path if it already exists - otherwise GenerateAssetUniquePath can return empty string
|
||||
// in event of path existing in a directory that is not yet created.
|
||||
if (FileUtility.Exists(path))
|
||||
path = AssetDatabase.GenerateUniqueAssetPath(path);
|
||||
|
||||
MaterialPalette newPalette = FileUtility.LoadRequired<MaterialPalette>(path);
|
||||
newPalette.array = Selection.GetFiltered(typeof(Material), SelectionMode.DeepAssets).Cast<Material>().ToArray();
|
||||
UnityEditor.EditorUtility.SetDirty(newPalette);
|
||||
EditorGUIUtility.PingObject(newPalette);
|
||||
}
|
||||
|
||||
public Material[] array;
|
||||
|
||||
public static implicit operator Material[](MaterialPalette materialArray)
|
||||
{
|
||||
return materialArray.array;
|
||||
}
|
||||
|
||||
public Material this[int i]
|
||||
{
|
||||
get { return array[i]; }
|
||||
set { array[i] = value; }
|
||||
}
|
||||
|
||||
public void SetDefaultValues()
|
||||
{
|
||||
array = new Material[10]
|
||||
{
|
||||
BuiltinMaterials.defaultMaterial,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3f1ca63a91b17724bbd99c8fa67e0180
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,352 @@
|
||||
// #define GENERATE_DESATURATED_ICONS
|
||||
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for any action that appears on the ProBuilder toolbar.
|
||||
/// </summary>
|
||||
public abstract class MenuAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Determines whether the menu item is visible, and if visible, whether it is enabled.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum MenuActionState
|
||||
{
|
||||
/// <summary>
|
||||
/// The button is not visible in the toolbar.
|
||||
/// </summary>
|
||||
Hidden = 0x0,
|
||||
/// <summary>
|
||||
/// The button is visible in the toolbar.
|
||||
/// </summary>
|
||||
Visible = 0x1,
|
||||
/// <summary>
|
||||
/// The button (and by proxy, the action it performs) are valid given the current selection.
|
||||
/// </summary>
|
||||
Enabled = 0x2,
|
||||
/// <summary>
|
||||
/// Button and action are both visible in the toolbar and valid given the current selection.
|
||||
/// </summary>
|
||||
VisibleAndEnabled = 0x3
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Path to the ProBuilder menu category.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use this where you wish to add a top level menu item.
|
||||
/// </remarks>
|
||||
internal const string probuilderMenuPath = "Tools/ProBuilder/";
|
||||
|
||||
/// <summary>
|
||||
/// The unicode character for the control key symbol on Windows, or command key on macOS.
|
||||
/// </summary>
|
||||
internal const char keyCommandSuper = PreferenceKeys.CMD_SUPER;
|
||||
|
||||
/// <summary>
|
||||
/// The unicode character for the shift key symbol.
|
||||
/// </summary>
|
||||
internal const char keyCommandShift = PreferenceKeys.CMD_SHIFT;
|
||||
|
||||
/// <summary>
|
||||
/// The unicode character for the option key symbol on macOS.
|
||||
/// </summary>
|
||||
/// <seealso cref="keyCommandAlt"/>
|
||||
internal const char keyCommandOption = PreferenceKeys.CMD_OPTION;
|
||||
|
||||
/// <summary>
|
||||
/// The unicode character for the alt key symbol on Windows.
|
||||
/// </summary>
|
||||
internal const char keyCommandAlt = PreferenceKeys.CMD_ALT;
|
||||
|
||||
/// <summary>
|
||||
/// The unicode character for the delete key symbol.
|
||||
/// </summary>
|
||||
internal const char keyCommandDelete = PreferenceKeys.CMD_DELETE;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the user selects an action to perform from the toolbar.
|
||||
/// </summary>
|
||||
public static Action<MenuAction> onPerformAction;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new button on the [ProBuilder toolbar](../manual/toolbar.html) in the Editor.
|
||||
/// </summary>
|
||||
protected MenuAction(){}
|
||||
|
||||
/// <summary>
|
||||
/// Compare two menu items precedence by their category and priority modifier.
|
||||
/// </summary>
|
||||
/// <param name="left"></param>
|
||||
/// <param name="right"></param>
|
||||
/// <returns></returns>
|
||||
internal static int CompareActionsByGroupAndPriority(MenuAction left, MenuAction right)
|
||||
{
|
||||
if (left == null)
|
||||
{
|
||||
if (right == null)
|
||||
return 0;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (right == null)
|
||||
return 1;
|
||||
|
||||
int l = (int)left.group, r = (int)right.group;
|
||||
|
||||
if (l < r)
|
||||
return -1;
|
||||
if (l > r)
|
||||
return 1;
|
||||
|
||||
int lp = left.toolbarPriority < 0 ? int.MaxValue : left.toolbarPriority,
|
||||
rp = right.toolbarPriority < 0 ? int.MaxValue : right.toolbarPriority;
|
||||
|
||||
return lp.CompareTo(rp);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the category assigned to this action.
|
||||
/// </summary>
|
||||
public abstract ToolbarGroup group { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value to optionally influence where this menu item appears in the toolbar.
|
||||
/// The default value is -1 (no preference).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 0 is first, 1 is second, -1 is no preference.
|
||||
/// </remarks>
|
||||
public virtual int toolbarPriority { get { return -1; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the icon to display in the Context Menu for this action.
|
||||
/// </summary>
|
||||
public abstract Texture2D icon { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the local path of the icon to display in the Context Menu for this action.
|
||||
/// </summary>
|
||||
public abstract string iconPath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the contents of the tooltip to display for this menu action.
|
||||
/// </summary>
|
||||
public abstract TooltipContent tooltip { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the override title for this action to display on the toolbar button.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If you don't implement this property, toolbar button displays the tooltip title.
|
||||
/// </remarks>
|
||||
public virtual string menuTitle { get { return tooltip.title; } }
|
||||
|
||||
/// <summary>
|
||||
/// This should be true for actions that have settings that can be changed, allowing the user
|
||||
/// to preview and confirm their changes. For actions that have no settings, this needs to
|
||||
/// be overridden to return false.
|
||||
/// </summary>
|
||||
protected internal virtual bool hasFileMenuEntry { get { return true; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag that indicates both the visibility and enabled state of an action
|
||||
/// to determine whether the current mode and selection is valid for it.
|
||||
/// </summary>
|
||||
/// <value>Flag indicating current state of the MenuItem.</value>
|
||||
public MenuActionState menuActionState
|
||||
{
|
||||
get
|
||||
{
|
||||
if (hidden)
|
||||
return MenuActionState.Hidden;
|
||||
if (enabled)
|
||||
return MenuActionState.VisibleAndEnabled;
|
||||
return MenuActionState.Visible;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the SelectMode states where this action applies. This drives the <see cref="hidden"/> property unless you override it.
|
||||
/// </summary>
|
||||
public virtual SelectMode validSelectModes
|
||||
{
|
||||
get { return SelectMode.Any; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether or not the action is valid given the current selection.
|
||||
///
|
||||
/// True if this action is valid with the current selection and mode.
|
||||
/// </summary>
|
||||
/// <seealso cref="hidden"/>
|
||||
public virtual bool enabled
|
||||
{
|
||||
get
|
||||
{;
|
||||
var b1 = ProBuilderEditor.selectMode.ContainsFlag(validSelectModes);
|
||||
var b2 = !ProBuilderEditor.selectMode.ContainsFlag(SelectMode.InputTool);
|
||||
return b1 && b2;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether this action is visible in the ProBuilder toolbar.
|
||||
///
|
||||
/// True if this action appears in the toolbar with the current mode and settings; false otherwise.
|
||||
/// </summary>
|
||||
/// <remarks>This returns false by default.</remarks>
|
||||
/// <seealso cref="enabled"/>
|
||||
public virtual bool hidden
|
||||
{
|
||||
get { return !ProBuilderEditor.selectMode.ContainsFlag(validSelectModes); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a flag that indicates whether the action implements extra options. If it does, it must also
|
||||
/// implement <see cref="OnSettingsGUI"/> so that an options indicator appears for this action button.
|
||||
/// </summary>
|
||||
protected virtual MenuActionState optionsMenuState
|
||||
{
|
||||
get { return MenuActionState.Hidden; }
|
||||
}
|
||||
|
||||
internal bool optionsVisible => optionsMenuState != MenuActionState.Hidden;
|
||||
internal bool optionsEnabled => optionsMenuState == MenuActionState.VisibleAndEnabled;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a checkmark status to the MenuItem generated in the GenerateMenuItems.cs script.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal virtual bool IsMenuItemChecked() => false;
|
||||
|
||||
/// <summary>
|
||||
/// Override the action automatically generated in the GenerateMenuItems.cs script.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal virtual string GetMenuItemOverride() => "";
|
||||
|
||||
/// <summary>
|
||||
/// Performs the action for this menu item. Use <see cref="PerformActionImplementation"/> to implement the action.
|
||||
/// Calling this method triggers the <see cref="onPerformAction"/> event.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Any new action classes that derive from this base class must also use the `PerformAction` method to register
|
||||
/// the new action in the Undo call stack.
|
||||
/// </remarks>
|
||||
/// <returns>A new ActionResult with a summary of the state of the action's success.</returns>
|
||||
public ActionResult PerformAction()
|
||||
{
|
||||
if(onPerformAction != null)
|
||||
onPerformAction(this);
|
||||
var res = PerformActionImplementation();
|
||||
ContentsChanged();
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the action for this menu item. Use this method to implement the action and then
|
||||
/// use <see cref="PerformAction"/> to call it.
|
||||
/// </summary>
|
||||
/// <returns>A new ActionResult with a summary of the state of the action's success.</returns>
|
||||
protected abstract ActionResult PerformActionImplementation();
|
||||
|
||||
const string obsoleteDoActionMsg = "DoAction() has been replaced by PerformAction(), the implementation of the action should inherit from PerformActionImplementation(). (UnityUpgradable) -> PerformAction()";
|
||||
/// <summary>
|
||||
/// Performs whatever action this menu item is supposed to do.
|
||||
/// </summary>
|
||||
/// <returns>A new ActionResult with a summary of the state of the action's success.</returns>
|
||||
[Obsolete(obsoleteDoActionMsg, false)]
|
||||
public ActionResult DoAction() => PerformAction();
|
||||
|
||||
/// <summary>
|
||||
/// Performs the action for this menu item when in Text mode.
|
||||
/// </summary>
|
||||
protected virtual void DoAlternateAction()
|
||||
{
|
||||
MenuOption.Show(OnSettingsGUI, OnSettingsEnable, OnSettingsDisable);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs the action for this menu item when in Text mode.
|
||||
/// </summary>
|
||||
public void PerformAltAction() => DoAlternateAction();
|
||||
|
||||
/// <summary>
|
||||
/// Replaces OnSettingsGUI for 2023.2 and newer to display settings in a SceneView overlay.
|
||||
/// Creates a custom settings window for this action. Populate a root visual element in that method with
|
||||
/// the settings content.
|
||||
/// </summary>
|
||||
/// <returns>A VisualElement containing settings content.</returns>
|
||||
public virtual VisualElement CreateSettingsContent()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If extra handles or gizmos are needed during the action execution in the scene, implement them here.
|
||||
/// </summary>
|
||||
/// <param name="sceneView">SceneView for which the DoSceneGUI method is called.</param>
|
||||
public virtual void DoSceneGUI(SceneView sceneView) {}
|
||||
|
||||
/// <summary>
|
||||
/// Implement the extra settings GUI for your action in this method.
|
||||
/// </summary>
|
||||
protected virtual void OnSettingsGUI() {}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the settings window is opened.
|
||||
/// </summary>
|
||||
protected virtual void OnSettingsEnable() {}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the settings window is closed.
|
||||
/// </summary>
|
||||
protected virtual void OnSettingsDisable() {}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the [Toolbar display mode](../manual/toolbar.html#toolbar-display-modes)
|
||||
/// is set to Icon mode (true) or Text mode (false).
|
||||
/// </summary>
|
||||
protected bool iconMode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Draws the menu item for this action in Text mode.
|
||||
/// </summary>
|
||||
/// <param name="options">Optional array of layout options for this menu item.</param>
|
||||
/// <returns>True if successful; false otherwise.</returns>
|
||||
[Obsolete]
|
||||
protected bool DoAltButton(params GUILayoutOption[] options)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when MenuAction contents change.
|
||||
/// </summary>
|
||||
public event Action changed;
|
||||
|
||||
/// <summary>
|
||||
/// Called during PerformAction.
|
||||
/// Calling this method triggers the <see cref="changed"/> event.
|
||||
/// </summary>
|
||||
protected void ContentsChanged() => changed?.Invoke();
|
||||
|
||||
/// <summary>
|
||||
/// Override to register <see cref="ContentsChanged"/> to event callbacks.
|
||||
/// </summary>
|
||||
public virtual void RegisterChangedCallbacks() { }
|
||||
|
||||
/// <summary>
|
||||
/// Override to unregister <see cref="ContentsChanged"/> from event callbacks.
|
||||
/// </summary>
|
||||
public virtual void UnregisterChangedCallbacks() { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9264fc220d4aaf645b44e788499db0db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,102 @@
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
static class MenuItems
|
||||
{
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 1 ", true,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 2 ", true,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 3 ", true,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 4 ", true,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 5 ", true,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 6 ", true,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 7 ", true,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 8 ", true,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 9 	", true,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 0 �", true,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static bool VerifyApplyVertexColor()
|
||||
{
|
||||
return ProBuilderEditor.instance != null && MeshSelection.selectedVertexCount > 0;
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 1 ", false,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static void MenuSetVertexColorPreset1()
|
||||
{
|
||||
VertexColorPalette.SetFaceColors(1);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 2 ", false,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static void MenuSetVertexColorPreset2()
|
||||
{
|
||||
VertexColorPalette.SetFaceColors(2);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 3 ", false,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static void MenuSetVertexColorPreset3()
|
||||
{
|
||||
VertexColorPalette.SetFaceColors(3);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 4 ", false,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static void MenuSetVertexColorPreset4()
|
||||
{
|
||||
VertexColorPalette.SetFaceColors(4);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 5 ", false,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static void MenuSetVertexColorPreset5()
|
||||
{
|
||||
VertexColorPalette.SetFaceColors(5);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 6 ", false,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static void MenuSetVertexColorPreset6()
|
||||
{
|
||||
VertexColorPalette.SetFaceColors(6);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 7 ", false,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static void MenuSetVertexColorPreset7()
|
||||
{
|
||||
VertexColorPalette.SetFaceColors(7);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 8 ", false,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static void MenuSetVertexColorPreset8()
|
||||
{
|
||||
VertexColorPalette.SetFaceColors(8);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 9 	", false,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static void MenuSetVertexColorPreset9()
|
||||
{
|
||||
VertexColorPalette.SetFaceColors(9);
|
||||
}
|
||||
|
||||
[MenuItem("Tools/" + PreferenceKeys.pluginTitle + "/Vertex Colors/Set Selected Faces to Preset 0 �", false,
|
||||
PreferenceKeys.menuVertexColors)]
|
||||
public static void MenuSetVertexColorPreset0()
|
||||
{
|
||||
VertexColorPalette.SetFaceColors(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05147f3a976fb174d987bc71dc70dee2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,81 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// Options menu window container. Do not instantiate this yourself, the toolbar will handle opening option windows.
|
||||
/// </summary>
|
||||
sealed class MenuOption : EditorWindow
|
||||
{
|
||||
System.Action onSettingsGUI = null;
|
||||
System.Action onSettingsDisable = null;
|
||||
|
||||
internal static MenuOption Show(System.Action onSettingsGUI, System.Action onSettingsEnable, System.Action onSettingsDisable)
|
||||
{
|
||||
MenuOption win = EditorWindow.GetWindow<MenuOption>(true, "Options", true);
|
||||
win.hideFlags = HideFlags.HideAndDontSave;
|
||||
|
||||
if (win.onSettingsDisable != null)
|
||||
win.onSettingsDisable();
|
||||
|
||||
if (onSettingsEnable != null)
|
||||
onSettingsEnable();
|
||||
|
||||
win.onSettingsDisable = onSettingsDisable;
|
||||
|
||||
win.onSettingsGUI = onSettingsGUI;
|
||||
|
||||
win.Show();
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Close any currently open option windows.
|
||||
/// </summary>
|
||||
public static void CloseAll()
|
||||
{
|
||||
foreach (MenuOption win in Resources.FindObjectsOfTypeAll<MenuOption>())
|
||||
win.Close();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
autoRepaintOnSceneChange = true;
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
if (onSettingsDisable != null)
|
||||
onSettingsDisable();
|
||||
}
|
||||
|
||||
void OnSelectionChange()
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
void OnHierarchyChange()
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
if (onSettingsGUI != null)
|
||||
{
|
||||
onSettingsGUI();
|
||||
}
|
||||
else if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
EditorApplication.delayCall += CloseAll;
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b2832c5edf831bd45a81ea86f2f3c0db
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
using ToolManager = UnityEditor.EditorTools.ToolManager;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class from which any action that is represented in the ProBuilder toolbar inherits.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A MenuToolToggle is a special action that creates an <see cref="UnityEditor.EditorTools.EditorTool"/> instance and sets it as the active tool.
|
||||
/// </remarks>
|
||||
public abstract class MenuToolToggle : MenuAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds a reference to the <see cref="UnityEditor.EditorTools.EditorTool"/> instance created by the action.
|
||||
/// </summary>
|
||||
protected EditorTool m_Tool;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a reference to the <see cref="UnityEditor.EditorTools.EditorTool"/> instance created by the action.
|
||||
/// </summary>
|
||||
public EditorTool Tool => m_Tool;
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override ActionResult PerformActionImplementation()
|
||||
{
|
||||
if(m_Tool == null)
|
||||
return ActionResult.NoSelection;
|
||||
|
||||
if(ToolManager.IsActiveTool(m_Tool))
|
||||
ToolManager.RestorePreviousTool();
|
||||
else
|
||||
ToolManager.SetActiveTool(m_Tool);
|
||||
|
||||
return ActionResult.Success;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 75ee05acc67ed8445b569a5aab0f3cc8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
using PHandleUtility = UnityEngine.ProBuilder.HandleUtility;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents the state of a ProBuilderMesh and it's selected elements.
|
||||
/// </summary>
|
||||
abstract class MeshAndElementSelection
|
||||
{
|
||||
ProBuilderMesh m_Mesh;
|
||||
|
||||
List<ElementGroup> m_ElementGroups;
|
||||
|
||||
public ProBuilderMesh mesh
|
||||
{
|
||||
get { return m_Mesh; }
|
||||
}
|
||||
|
||||
public List<ElementGroup> elementGroups
|
||||
{
|
||||
get { return m_ElementGroups; }
|
||||
}
|
||||
|
||||
public MeshAndElementSelection(ProBuilderMesh mesh, bool collectCoincidentIndices)
|
||||
{
|
||||
m_Mesh = mesh;
|
||||
m_ElementGroups = ElementGroup.GetElementGroups(mesh, collectCoincidentIndices);
|
||||
}
|
||||
}
|
||||
|
||||
class ElementGroup
|
||||
{
|
||||
List<int> m_Indices;
|
||||
Vector3 m_Position;
|
||||
Quaternion m_Rotation;
|
||||
|
||||
/// <value>
|
||||
/// The pivot of this selection in world space.
|
||||
/// </value>
|
||||
public Vector3 position
|
||||
{
|
||||
get { return m_Position; }
|
||||
}
|
||||
|
||||
/// <value>
|
||||
/// The rotation of this element group in world space.
|
||||
/// </value>
|
||||
public Quaternion rotation
|
||||
{
|
||||
get { return m_Rotation; }
|
||||
}
|
||||
|
||||
public IEnumerable<int> indices
|
||||
{
|
||||
get { return m_Indices; }
|
||||
}
|
||||
|
||||
public ElementGroup(List<int> indices, Vector3 pivot, Quaternion rotation)
|
||||
{
|
||||
m_Indices = indices;
|
||||
m_Position = pivot;
|
||||
m_Rotation = rotation;
|
||||
}
|
||||
|
||||
public static List<ElementGroup> GetElementGroups(ProBuilderMesh mesh, bool collectCoincident)
|
||||
{
|
||||
var groups = new List<ElementGroup>();
|
||||
var selectMode = ProBuilderEditor.selectMode;
|
||||
|
||||
if (selectMode.ContainsFlag(SelectMode.Vertex | SelectMode.TextureVertex))
|
||||
{
|
||||
foreach (var list in GetVertexSelectionGroups(mesh, collectCoincident))
|
||||
{
|
||||
var pos = PHandleUtility.GetActiveElementPosition(mesh, list);
|
||||
var rot = PHandleUtility.GetVertexRotation(mesh, HandleOrientation.ActiveElement, list);
|
||||
groups.Add(new ElementGroup(list, pos, rot));
|
||||
}
|
||||
}
|
||||
else if (selectMode.ContainsFlag(SelectMode.Edge | SelectMode.TextureEdge))
|
||||
{
|
||||
foreach (var list in GetEdgeSelectionGroups(mesh))
|
||||
{
|
||||
var pos = PHandleUtility.GetActiveElementPosition(mesh, list);
|
||||
var rot = PHandleUtility.GetEdgeRotation(mesh, HandleOrientation.ActiveElement, list);
|
||||
|
||||
List<int> indices;
|
||||
|
||||
if (collectCoincident)
|
||||
{
|
||||
indices = new List<int>();
|
||||
mesh.GetCoincidentVertices(list, indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
indices = list.SelectMany(x => new int[] { x.a, x.b }).ToList();
|
||||
}
|
||||
|
||||
groups.Add(new ElementGroup(indices, pos, rot));
|
||||
}
|
||||
}
|
||||
else if (selectMode.ContainsFlag(SelectMode.Face | SelectMode.TextureFace))
|
||||
{
|
||||
foreach (var list in GetFaceSelectionGroups(mesh))
|
||||
{
|
||||
var pos = PHandleUtility.GetActiveElementPosition(mesh, list);
|
||||
var rot = PHandleUtility.GetFaceRotation(mesh, HandleOrientation.ActiveElement, list);
|
||||
List<int> indices;
|
||||
|
||||
if (collectCoincident)
|
||||
{
|
||||
indices = new List<int>();
|
||||
mesh.GetCoincidentVertices(list, indices);
|
||||
}
|
||||
else
|
||||
{
|
||||
indices = list.SelectMany(x => x.distinctIndexesInternal).ToList();
|
||||
}
|
||||
|
||||
groups.Add(new ElementGroup(indices, pos, rot));
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
static IEnumerable<List<Face>> GetFaceSelectionGroups(ProBuilderMesh mesh)
|
||||
{
|
||||
var wings = WingedEdge.GetWingedEdges(mesh, mesh.selectedFacesInternal, true);
|
||||
var filter = new HashSet<Face>();
|
||||
var groups = new List<List<Face>>();
|
||||
var groupIdx = -1;
|
||||
var i = -1;
|
||||
|
||||
foreach (var wing in wings)
|
||||
{
|
||||
var group = new List<Face>() {};
|
||||
CollectAdjacentFaces(wing, filter, group);
|
||||
if (group.Count > 0)
|
||||
{
|
||||
i++;
|
||||
// Make sure the last selected face is the last in the group
|
||||
var idx = group.IndexOf(mesh.selectedFacesInternal[mesh.selectedFacesInternal.Length - 1]);
|
||||
if (idx != -1 && idx < group.Count)
|
||||
{
|
||||
var item = group[idx];
|
||||
groupIdx = i;
|
||||
group[idx] = group[group.Count - 1];
|
||||
group[group.Count - 1] = item;
|
||||
}
|
||||
groups.Add(group);
|
||||
}
|
||||
}
|
||||
// Make sure the last selected face's group is the last in the groups
|
||||
if (groupIdx != -1 && groupIdx < groups.Count)
|
||||
{
|
||||
var item = groups[groupIdx];
|
||||
groups[groupIdx] = groups[groups.Count - 1];
|
||||
groups[groups.Count - 1] = item;
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
static IEnumerable<List<int>> GetVertexSelectionGroups(ProBuilderMesh mesh, bool collectCoincident)
|
||||
{
|
||||
if (!collectCoincident)
|
||||
return mesh.selectedIndexesInternal.Select(x => new List<int>() { x }).ToList();
|
||||
|
||||
var shared = mesh.selectedSharedVertices;
|
||||
|
||||
var groups = new List<List<int>>();
|
||||
var groupIdx = -1;
|
||||
var i = -1;
|
||||
foreach (var index in shared)
|
||||
{
|
||||
var coincident = new List<int>();
|
||||
mesh.GetCoincidentVertices(mesh.sharedVerticesInternal[index][0], coincident);
|
||||
groups.Add(coincident);
|
||||
i++;
|
||||
// Make sure the last selected vertic is the last in the group
|
||||
var idx = coincident.IndexOf(mesh.selectedIndexesInternal[mesh.selectedIndexesInternal.Length - 1]);
|
||||
if (idx != -1 && idx < coincident.Count)
|
||||
{
|
||||
var item = coincident[idx];
|
||||
groupIdx = i;
|
||||
coincident[idx] = coincident[coincident.Count - 1];
|
||||
coincident[coincident.Count - 1] = item;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the last selected vertic's group is the last in the groups
|
||||
if (groupIdx != -1 && groupIdx < groups.Count)
|
||||
{
|
||||
var item = groups[groupIdx];
|
||||
groups[groupIdx] = groups[groups.Count - 1];
|
||||
groups[groups.Count - 1] = item;
|
||||
}
|
||||
|
||||
return groups;
|
||||
}
|
||||
|
||||
static IEnumerable<List<Edge>> GetEdgeSelectionGroups(ProBuilderMesh mesh)
|
||||
{
|
||||
var edges = EdgeLookup.GetEdgeLookup(mesh.selectedEdgesInternal, mesh.sharedVertexLookup);
|
||||
var groups = new List<SimpleTuple<HashSet<int>, List<Edge>>>();
|
||||
|
||||
foreach (var edge in edges)
|
||||
{
|
||||
var foundMatch = false;
|
||||
|
||||
foreach (var kvp in groups)
|
||||
{
|
||||
if (kvp.item1.Contains(edge.common.a) || kvp.item1.Contains(edge.common.b))
|
||||
{
|
||||
kvp.item1.Add(edge.common.a);
|
||||
kvp.item1.Add(edge.common.b);
|
||||
kvp.item2.Add(edge.local);
|
||||
foundMatch = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundMatch)
|
||||
groups.Add(new SimpleTuple<HashSet<int>, List<Edge>>(
|
||||
new HashSet<int>() { edge.common.a, edge.common.b },
|
||||
new List<Edge>() { edge.local }));
|
||||
}
|
||||
|
||||
// collect overlapping groups (happens in cases where selection order begins as two separate groups but
|
||||
// becomes one)
|
||||
var res = new List<List<Edge>>();
|
||||
var overlap = new HashSet<int>();
|
||||
var j = -1;
|
||||
var groupIdx = -1;
|
||||
|
||||
for (int i = 0, c = groups.Count; i < c; i++)
|
||||
{
|
||||
if (overlap.Contains(i))
|
||||
continue;
|
||||
|
||||
List<Edge> grp = groups[i].item2;
|
||||
|
||||
for (int n = i + 1; n < c; n++)
|
||||
{
|
||||
if (groups[i].item1.Overlaps(groups[n].item1))
|
||||
{
|
||||
overlap.Add(n);
|
||||
grp.AddRange(groups[n].item2);
|
||||
}
|
||||
}
|
||||
j++;
|
||||
// Make sure the last selected edge is the last in the group
|
||||
var idx = grp.IndexOf(mesh.selectedEdgesInternal[mesh.selectedEdgesInternal.Length - 1]);
|
||||
if (idx != -1 && idx < grp.Count)
|
||||
{
|
||||
var item = grp[idx];
|
||||
groupIdx = j;
|
||||
grp[idx] = grp[grp.Count - 1];
|
||||
grp[grp.Count - 1] = item;
|
||||
}
|
||||
res.Add(grp);
|
||||
}
|
||||
|
||||
// Make sure the last selected edge's group is the last in the groups
|
||||
if (groupIdx != -1 && groupIdx < res.Count)
|
||||
{
|
||||
var item = res[groupIdx];
|
||||
res[groupIdx] = res[res.Count - 1];
|
||||
res[res.Count - 1] = item;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static void CollectAdjacentFaces(WingedEdge wing, HashSet<Face> filter, List<Face> group)
|
||||
{
|
||||
if (!filter.Add(wing.face))
|
||||
return;
|
||||
|
||||
group.Add(wing.face);
|
||||
|
||||
var enumerator = new WingedEdgeEnumerator(wing);
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
var opposite = enumerator.Current.opposite;
|
||||
if (opposite == null)
|
||||
continue;
|
||||
CollectAdjacentFaces(opposite, filter, group);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d1266b9f3f7ac478b97ce34f935eadc3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor.EditorTools;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.ProBuilder.MeshOperations;
|
||||
using Math = UnityEngine.ProBuilder.Math;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
class MeshDebugTool : EditorTool
|
||||
{
|
||||
[MenuItem("Tools/ProBuilder/Repair/Mesh Debug Tool", false, PreferenceKeys.menuRepair + 100)]
|
||||
static void Init() => ToolManager.SetActiveTool<MeshDebugTool>();
|
||||
|
||||
ProBuilderMesh[] m_Meshes;
|
||||
List<InvalidFaceInfo>[] m_Invalid;
|
||||
|
||||
struct InvalidFaceInfo
|
||||
{
|
||||
public Vector3 position;
|
||||
public int faceIndex;
|
||||
public string problems;
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
Selection.selectionChanged += UpdateSelection;
|
||||
UpdateSelection();
|
||||
}
|
||||
|
||||
void OnDisable()
|
||||
{
|
||||
Selection.selectionChanged -= UpdateSelection;
|
||||
}
|
||||
|
||||
void UpdateSelection()
|
||||
{
|
||||
m_Meshes = Selection.GetFiltered<ProBuilderMesh>(SelectionMode.TopLevel);
|
||||
m_Invalid = new List<InvalidFaceInfo>[m_Meshes.Length];
|
||||
|
||||
for (int i = 0, c = m_Meshes.Length; i < c; ++i)
|
||||
{
|
||||
m_Invalid[i] = new List<InvalidFaceInfo>();
|
||||
var faces = m_Meshes[i].facesInternal;
|
||||
for (int n = 0, f = faces.Length; n < f; n++)
|
||||
if (CheckFaceVertexAttributes(m_Meshes[i], faces[n], out var info))
|
||||
m_Invalid[i].Add(info);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnToolGUI(EditorWindow window)
|
||||
{
|
||||
var evt = Event.current;
|
||||
|
||||
DoSceneViewOverlay();
|
||||
|
||||
for(int i = 0, c = m_Meshes.Length; i < c; ++i)
|
||||
{
|
||||
using var matrix = new Handles.DrawingScope(m_Meshes[i].transform.localToWorldMatrix);
|
||||
|
||||
foreach (var info in m_Invalid[i])
|
||||
{
|
||||
Handles.DotHandleCap(0, info.position, Quaternion.identity, HandleUtility.GetHandleSize(info.position), evt.type);
|
||||
Handles.ShowSceneViewLabel(info.position, EditorGUIUtility.TempContent(info.problems));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DoSceneViewOverlay()
|
||||
{
|
||||
Handles.BeginGUI();
|
||||
GUILayout.Space(4);
|
||||
GUILayout.BeginHorizontal();
|
||||
GUILayout.Space(4);
|
||||
GUILayout.BeginVertical(EditorStyles.helpBox);
|
||||
if (m_Meshes.Length > 0)
|
||||
{
|
||||
for (int i = 0, c = m_Meshes.Length; i < c; ++i)
|
||||
{
|
||||
GUILayout.BeginHorizontal();
|
||||
if (m_Invalid[i].Count > 0 && GUILayout.Button("Fix"))
|
||||
{
|
||||
MeshValidation.EnsureMeshIsValid(m_Meshes[i], out var removedVertexCount);
|
||||
Debug.Log($"Successfully repaired {m_Meshes[i].name}. Removed {removedVertexCount} problem vertices.");
|
||||
}
|
||||
GUILayout.Label($"Mesh \"{m_Meshes[i].name}\" found {m_Invalid[i].Count} problems");
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("Select a ProBuilder Mesh");
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
GUILayout.FlexibleSpace();
|
||||
GUILayout.EndHorizontal();
|
||||
Handles.EndGUI();
|
||||
}
|
||||
|
||||
bool CheckFaceVertexAttributes(ProBuilderMesh mesh, Face face, out InvalidFaceInfo info)
|
||||
{
|
||||
info = default;
|
||||
var positions = mesh.positionsInternal;
|
||||
var normals = mesh.normalsInternal;
|
||||
var tangents = mesh.tangentsInternal;
|
||||
var textures = mesh.texturesInternal;
|
||||
var indices = face.indexesInternal;
|
||||
var distinct = face.distinctIndexesInternal;
|
||||
|
||||
for (int i = 0, c = indices.Length; i < c; i+=3)
|
||||
{
|
||||
if (Math.TriangleArea(positions[indices[i]], positions[indices[i+1]], positions[indices[i+2]]) < float.Epsilon)
|
||||
info.problems += $"Degenerate Triangle {indices[i]} {indices[i+1]} {indices[i+2]}";
|
||||
}
|
||||
|
||||
for (int i = 0, c = distinct.Length; i < c; ++i)
|
||||
{
|
||||
if (normals != null && !Math.IsNumber(normals[i]))
|
||||
info.problems += $"normals [{i}] is NaN";
|
||||
if (textures != null && !Math.IsNumber(textures[i]))
|
||||
info.problems += $"uv0 [{i}] is NaN";
|
||||
if (tangents != null && !Math.IsNumber(tangents[i]))
|
||||
info.problems += $"tangents [{i}] is NaN";
|
||||
}
|
||||
|
||||
info.position = Math.Average(positions, distinct);
|
||||
|
||||
return !string.IsNullOrEmpty(info.problems);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 05fa18d180bd4dcea12922b6cff680e2
|
||||
timeCreated: 1626298113
|
||||
@@ -0,0 +1,676 @@
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine.ProBuilder;
|
||||
using UnityEngine.ProBuilder.MeshOperations;
|
||||
using EditorToolManager = UnityEditor.EditorTools.EditorToolManager;
|
||||
using ToolManager = UnityEditor.EditorTools.ToolManager;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides helper functions for selecting Unity objects and ProBuilder mesh elements.
|
||||
/// </summary>
|
||||
[InitializeOnLoad]
|
||||
public static class MeshSelection
|
||||
{
|
||||
static List<ProBuilderMesh> s_TopSelection = new List<ProBuilderMesh>();
|
||||
static ProBuilderMesh s_ActiveMesh;
|
||||
static List<MeshAndElementSelection> s_ElementSelection = new List<MeshAndElementSelection>();
|
||||
|
||||
static bool s_ElementCountsDirty = true;
|
||||
static bool s_SelectedElementGroupsDirty = true;
|
||||
static bool s_SelectedFacesInEditAreaDirty = true;
|
||||
static bool s_SelectionBoundsDirty = true;
|
||||
|
||||
static Bounds s_SelectionBounds = new Bounds();
|
||||
static Dictionary<ProBuilderMesh, List<Face>> s_SelectedFacesInEditArea = new Dictionary<ProBuilderMesh, List<Face>>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the axis-aligned bounding box encompassing the selected elements.
|
||||
/// </summary>
|
||||
public static Bounds bounds
|
||||
{
|
||||
get
|
||||
{
|
||||
if(s_SelectionBoundsDirty)
|
||||
RecalculateSelectionBounds();
|
||||
return s_SelectionBounds;
|
||||
}
|
||||
}
|
||||
|
||||
static int s_SelectedObjectCount;
|
||||
static int s_SelectedVertexCount;
|
||||
static int s_SelectedSharedVertexCount;
|
||||
static int s_SelectedFaceCount;
|
||||
static int s_SelectedEdgeCount;
|
||||
|
||||
static int s_SelectedFaceCountObjectMax;
|
||||
static int s_SelectedEdgeCountObjectMax;
|
||||
static int s_SelectedVertexCountObjectMax;
|
||||
static int s_SelectedSharedVertexCountObjectMax;
|
||||
static int s_SelectedCoincidentVertexCountMax;
|
||||
|
||||
static int s_TotalVertexCount;
|
||||
static int s_TotalFaceCount;
|
||||
static int s_TotalEdgeCount;
|
||||
static int s_TotalCommonVertexCount;
|
||||
static int s_TotalVertexCountCompiled;
|
||||
static int s_TotalTriangleCountCompiled;
|
||||
|
||||
/// <summary>
|
||||
/// Gets how many ProBuilderMesh components are currently selected.
|
||||
/// </summary>
|
||||
public static int selectedObjectCount { get { CacheElementCounts(); return s_SelectedObjectCount; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sum of all currently selected vertices on all currently selected ProBuilderMesh objects.
|
||||
/// </summary>
|
||||
/// <seealso cref="selectedSharedVertexCount"/>
|
||||
public static int selectedVertexCount { get { CacheElementCounts(); return s_SelectedVertexCount; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sum of all currently selected shared vertices on all currently selected ProBuilderMesh objects.
|
||||
/// </summary>
|
||||
/// <seealso cref="selectedVertexCount"/>
|
||||
public static int selectedSharedVertexCount { get { CacheElementCounts(); return s_SelectedSharedVertexCount; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sum of all currently selected faces on all currently selected ProBuilderMesh objects.
|
||||
/// </summary>
|
||||
public static int selectedFaceCount { get { CacheElementCounts(); return s_SelectedFaceCount; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sum of all currently selected edges on all currently selected ProBuilderMesh objects.
|
||||
/// </summary>
|
||||
public static int selectedEdgeCount { get { CacheElementCounts(); return s_SelectedEdgeCount; } }
|
||||
|
||||
// per-object selected element maxes
|
||||
internal static int selectedFaceCountObjectMax { get { CacheElementCounts(); return s_SelectedFaceCountObjectMax; } }
|
||||
internal static int selectedEdgeCountObjectMax { get { CacheElementCounts(); return s_SelectedEdgeCountObjectMax; } }
|
||||
internal static int selectedVertexCountObjectMax { get { CacheElementCounts(); return s_SelectedVertexCountObjectMax; } }
|
||||
internal static int selectedSharedVertexCountObjectMax { get { CacheElementCounts(); return s_SelectedSharedVertexCountObjectMax; } }
|
||||
internal static int selectedCoincidentVertexCountMax { get { CacheElementCounts(); return s_SelectedCoincidentVertexCountMax; } }
|
||||
|
||||
// Faces that need to be refreshed when moving or modifying the actual selection
|
||||
internal static Dictionary<ProBuilderMesh, List<Face>> selectedFacesInEditZone
|
||||
{
|
||||
get
|
||||
{
|
||||
if(s_SelectedFacesInEditAreaDirty)
|
||||
RecalculateFacesInEditableArea();
|
||||
return s_SelectedFacesInEditArea;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void InvalidateCaches()
|
||||
{
|
||||
s_ElementCountsDirty = true;
|
||||
s_SelectedElementGroupsDirty = true;
|
||||
s_SelectedFacesInEditAreaDirty = true;
|
||||
s_SelectionBoundsDirty = true;
|
||||
}
|
||||
|
||||
internal static IEnumerable<MeshAndElementSelection> elementSelection
|
||||
{
|
||||
get
|
||||
{
|
||||
RecalculateSelectedElementGroups();
|
||||
return s_ElementSelection;
|
||||
}
|
||||
}
|
||||
|
||||
static MeshSelection()
|
||||
{
|
||||
Selection.selectionChanged += OnObjectSelectionChanged;
|
||||
Undo.undoRedoPerformed += UndoRedoPerformed;
|
||||
ProBuilderMesh.elementSelectionChanged += ElementSelectionChanged;
|
||||
PrefabUtility.prefabInstanceReverted += PrefabInstanceReverted;
|
||||
EditorMeshUtility.meshOptimized += (x, y) => { s_ElementCountsDirty = true; };
|
||||
ProBuilderMesh.componentWillBeDestroyed += RemoveMeshFromSelectionInternal;
|
||||
ProBuilderMesh.componentHasBeenReset += RefreshSelectionAfterComponentReset;
|
||||
ProBuilderEditor.selectModeChanged += SelectModeChanged;
|
||||
ToolManager.activeToolChanged += ActiveToolChanged;
|
||||
ToolManager.activeContextChanged += ActiveToolChanged;
|
||||
VertexManipulationTool.afterMeshModification += AfterMeshModification;
|
||||
PrefabStage.prefabStageOpened += OnPrefabStageChanged;
|
||||
PrefabStage.prefabStageClosing += OnPrefabStageChanged;
|
||||
OnObjectSelectionChanged();
|
||||
}
|
||||
|
||||
[InitializeOnEnterPlayMode]
|
||||
static void ResetStaticsOnLoad()
|
||||
{
|
||||
s_SelectedFacesInEditArea.Clear();
|
||||
s_SelectedObjectCount = 0;
|
||||
s_SelectedVertexCount = 0;
|
||||
s_SelectedSharedVertexCount = 0;
|
||||
s_SelectedFaceCount = 0;
|
||||
s_SelectedEdgeCount = 0;
|
||||
|
||||
s_SelectedFaceCountObjectMax = 0;
|
||||
s_SelectedEdgeCountObjectMax = 0;
|
||||
s_SelectedVertexCountObjectMax = 0;
|
||||
s_SelectedSharedVertexCountObjectMax = 0;
|
||||
s_SelectedCoincidentVertexCountMax = 0;
|
||||
|
||||
s_TotalVertexCount = 0;
|
||||
s_TotalFaceCount = 0;
|
||||
s_TotalEdgeCount = 0;
|
||||
s_TotalCommonVertexCount = 0;
|
||||
s_TotalVertexCountCompiled = 0;
|
||||
s_TotalTriangleCountCompiled = 0;
|
||||
s_SelectionBounds = new Bounds();
|
||||
|
||||
OnObjectSelectionChanged();
|
||||
}
|
||||
|
||||
static void PrefabInstanceReverted(GameObject obj)
|
||||
{
|
||||
if (obj.TryGetComponent<ProBuilderMesh>(out _))
|
||||
OnObjectSelectionChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ProBuilder mesh on the active selected GameObject.
|
||||
/// </summary>
|
||||
public static ProBuilderMesh activeMesh
|
||||
{
|
||||
get
|
||||
{
|
||||
// If shift selecting between objects already selected there won't be an OnObjectSelectionChanged
|
||||
// triggered which might lead to Selection.activeGameObject and s_ActiveMesh to be out of sync.
|
||||
// This check below is to handle this situation.
|
||||
GameObject activeGo = (s_ActiveMesh ? s_ActiveMesh.gameObject : null);
|
||||
|
||||
if (activeGo != Selection.activeGameObject)
|
||||
s_ActiveMesh = Selection.activeGameObject != null ? Selection.activeGameObject.GetComponent<ProBuilderMesh>() : null;
|
||||
|
||||
return s_ActiveMesh;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Face activeFace
|
||||
{
|
||||
get { return activeMesh != null ? activeMesh.selectedFacesInternal.LastOrDefault() : null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the object selection changes.
|
||||
/// </summary>
|
||||
public static event System.Action objectSelectionChanged;
|
||||
|
||||
static HashSet<ProBuilderMesh> s_UnitySelectionChangeMeshes = new HashSet<ProBuilderMesh>();
|
||||
|
||||
internal static void OnObjectSelectionChanged()
|
||||
{
|
||||
// GameObjects returns both parent and child when both are selected, where transforms only returns the
|
||||
// top-most transform.
|
||||
s_UnitySelectionChangeMeshes.Clear();
|
||||
s_ElementSelection.Clear();
|
||||
s_ActiveMesh = null;
|
||||
|
||||
var gameObjects = Selection.gameObjects;
|
||||
|
||||
for (int i = 0, c = gameObjects.Length; i < c; i++)
|
||||
{
|
||||
if(gameObjects[i].TryGetComponent<ProBuilderMesh>(out var mesh))
|
||||
{
|
||||
if (gameObjects[i] == Selection.activeGameObject)
|
||||
s_ActiveMesh = mesh;
|
||||
|
||||
s_UnitySelectionChangeMeshes.Add(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0, c = s_TopSelection.Count; i < c; i++)
|
||||
{
|
||||
if (!s_UnitySelectionChangeMeshes.Contains(s_TopSelection[i]))
|
||||
{
|
||||
if(s_TopSelection[i] != null)
|
||||
UndoUtility.RecordSelection(s_TopSelection[i], "Selection Change");
|
||||
s_TopSelection[i].ClearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
s_TopSelection.Clear();
|
||||
|
||||
foreach (var mesh in s_UnitySelectionChangeMeshes)
|
||||
{
|
||||
// don't add prefabs or assets to the mesh selection
|
||||
if (string.IsNullOrEmpty(AssetDatabase.GetAssetPath(mesh.gameObject)))
|
||||
s_TopSelection.Add(mesh);
|
||||
}
|
||||
|
||||
InvalidateCaches();
|
||||
|
||||
if (objectSelectionChanged != null)
|
||||
objectSelectionChanged();
|
||||
|
||||
s_UnitySelectionChangeMeshes.Clear();
|
||||
}
|
||||
|
||||
static void SelectModeChanged(SelectMode mode) => InvalidateCaches();
|
||||
|
||||
static void ActiveToolChanged() => InvalidateCaches();
|
||||
|
||||
static void AfterMeshModification(IEnumerable<ProBuilderMesh> selection) => InvalidateCaches();
|
||||
|
||||
//Ensure the selection is cleaned when entering/exiting a prefab stage
|
||||
static void OnPrefabStageChanged(PrefabStage _) => InvalidateCaches();
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the mesh selection matches the current Unity selection. Called after Undo/Redo, as adding or removing
|
||||
/// mesh components can cause the selection to de-sync without emitting a selection changed event.
|
||||
/// </summary>
|
||||
internal static void UndoRedoPerformed()
|
||||
{
|
||||
var willInvokeChangeCallback = false;
|
||||
for (int i = 0; i < topInternal.Count; i++)
|
||||
{
|
||||
if (topInternal[i] == null || !Selection.Contains(topInternal[i].gameObject))
|
||||
{
|
||||
EditorApplication.delayCall += OnObjectSelectionChanged;
|
||||
willInvokeChangeCallback = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!willInvokeChangeCallback)
|
||||
{
|
||||
var activeMesh = Selection.activeGameObject != null ? Selection.activeGameObject.GetComponent<ProBuilderMesh>() : null;
|
||||
if (activeMesh != null && !topInternal.Contains(activeMesh) || (activeMesh == null && topInternal.Count == 0))
|
||||
EditorApplication.delayCall += OnObjectSelectionChanged;
|
||||
}
|
||||
|
||||
InvalidateCaches();
|
||||
}
|
||||
|
||||
static void ElementSelectionChanged(ProBuilderMesh mesh)
|
||||
{
|
||||
InvalidateCaches();
|
||||
}
|
||||
|
||||
internal static void RecalculateSelectedElementGroups()
|
||||
{
|
||||
if (!s_SelectedElementGroupsDirty)
|
||||
return;
|
||||
|
||||
s_SelectedElementGroupsDirty = false;
|
||||
s_ElementSelection.Clear();
|
||||
|
||||
VertexManipulationTool activeTool = null;
|
||||
var editorTool = EditorToolManager.activeTool;
|
||||
if(editorTool is VertexManipulationTool)
|
||||
activeTool = (VertexManipulationTool)editorTool;
|
||||
|
||||
if (activeTool != null)
|
||||
{
|
||||
foreach (var mesh in s_TopSelection)
|
||||
s_ElementSelection.Add(activeTool.GetElementSelection(mesh, VertexManipulationTool.pivotPoint));
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CacheElementCounts(bool forceRebuild = false)
|
||||
{
|
||||
if (!s_ElementCountsDirty && !forceRebuild)
|
||||
return;
|
||||
|
||||
s_SelectedFaceCount = 0;
|
||||
s_SelectedEdgeCount = 0;
|
||||
s_SelectedVertexCount = 0;
|
||||
s_SelectedSharedVertexCount = 0;
|
||||
|
||||
s_SelectedVertexCountObjectMax = 0;
|
||||
s_SelectedSharedVertexCountObjectMax = 0;
|
||||
s_SelectedCoincidentVertexCountMax = 0;
|
||||
s_SelectedFaceCountObjectMax = 0;
|
||||
s_SelectedEdgeCountObjectMax = 0;
|
||||
|
||||
s_TotalVertexCount = 0;
|
||||
s_TotalFaceCount = 0;
|
||||
s_TotalEdgeCount = 0;
|
||||
s_TotalCommonVertexCount = 0;
|
||||
s_TotalVertexCountCompiled = 0;
|
||||
s_TotalTriangleCountCompiled = 0;
|
||||
|
||||
s_SelectedObjectCount = topInternal.Count;
|
||||
|
||||
for (var i = 0; i < s_SelectedObjectCount; i++)
|
||||
{
|
||||
var mesh = topInternal[i];
|
||||
|
||||
s_SelectedFaceCount += mesh.selectedFaceCount;
|
||||
s_SelectedEdgeCount += mesh.selectedEdgeCount;
|
||||
s_SelectedVertexCount += mesh.selectedIndexesInternal.Length;
|
||||
s_SelectedSharedVertexCount += mesh.selectedSharedVerticesCount;
|
||||
|
||||
s_SelectedVertexCountObjectMax = System.Math.Max(s_SelectedVertexCountObjectMax, mesh.selectedIndexesInternal.Length);
|
||||
s_SelectedSharedVertexCountObjectMax = System.Math.Max(s_SelectedSharedVertexCountObjectMax, mesh.selectedSharedVerticesCount);
|
||||
s_SelectedCoincidentVertexCountMax = System.Math.Max(s_SelectedCoincidentVertexCountMax, mesh.selectedCoincidentVertexCount);
|
||||
s_SelectedFaceCountObjectMax = System.Math.Max(s_SelectedFaceCountObjectMax, mesh.selectedFaceCount);
|
||||
s_SelectedEdgeCountObjectMax = System.Math.Max(s_SelectedEdgeCountObjectMax, mesh.selectedEdgeCount);
|
||||
|
||||
// element total counts
|
||||
s_TotalVertexCount += mesh.vertexCount;
|
||||
s_TotalFaceCount += mesh.faceCount;
|
||||
s_TotalEdgeCount += mesh.edgeCount;
|
||||
s_TotalCommonVertexCount += mesh.sharedVerticesInternal.Length;
|
||||
s_TotalVertexCountCompiled += mesh.mesh == null ? 0 : mesh.mesh.vertexCount;
|
||||
s_TotalTriangleCountCompiled += (int) UnityEngine.ProBuilder.MeshUtility.GetPrimitiveCount(mesh.mesh);
|
||||
}
|
||||
|
||||
s_ElementCountsDirty = false;
|
||||
}
|
||||
|
||||
internal static void RecalculateSelectionBounds()
|
||||
{
|
||||
s_SelectionBoundsDirty = false;
|
||||
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]]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void RecalculateFacesInEditableArea()
|
||||
{
|
||||
s_SelectedFacesInEditAreaDirty = false;
|
||||
s_SelectedFacesInEditArea.Clear();
|
||||
|
||||
foreach (var mesh in topInternal)
|
||||
{
|
||||
s_SelectedFacesInEditArea.Add(mesh, ElementSelection.GetNeighborFaces(mesh, mesh.selectedIndexesInternal));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all selected ProBuilderMesh components. Corresponds to <![CDATA[Selection.gameObjects.Select(x => x.GetComponent<ProBuilderMesh>().Where(y => y != null);]]>.
|
||||
/// </summary>
|
||||
/// <value>An array of the currently selected ProBuilderMesh components. Does not include children of selected objects.</value>
|
||||
public static IEnumerable<ProBuilderMesh> top
|
||||
{
|
||||
get { return new ReadOnlyCollection<ProBuilderMesh>(s_TopSelection); }
|
||||
}
|
||||
|
||||
internal static List<ProBuilderMesh> topInternal
|
||||
{
|
||||
get { return s_TopSelection; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all selected ProBuilderMesh components, including those on the children of selected objects.
|
||||
/// </summary>
|
||||
/// <value>All selected ProBuilderMesh components, including those on the children of selected objects.</value>
|
||||
public static IEnumerable<ProBuilderMesh> deep
|
||||
{
|
||||
get { return Selection.gameObjects.SelectMany(x => x.GetComponentsInChildren<ProBuilderMesh>()); }
|
||||
}
|
||||
|
||||
internal static bool Contains(ProBuilderMesh mesh)
|
||||
{
|
||||
return s_TopSelection.Contains(mesh);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sum of the vertices across all selected meshes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is <see cref="ProBuilderMesh.vertexCount"/>, not <see cref="UnityEngine.Mesh.vertexCount"/>.
|
||||
/// </remarks>
|
||||
public static int totalVertexCount { get { CacheElementCounts(); return s_TotalVertexCount; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of all selected vertices across the selected ProBuilder meshes, excluding coincident duplicates.
|
||||
/// </summary>
|
||||
public static int totalCommonVertexCount { get { CacheElementCounts(); return s_TotalCommonVertexCount; } }
|
||||
|
||||
internal static int totalVertexCountOptimized { get { CacheElementCounts(); return s_TotalVertexCountCompiled; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sum of all selected ProBuilderMesh faces.
|
||||
/// </summary>
|
||||
public static int totalFaceCount { get { CacheElementCounts(); return s_TotalFaceCount; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sum of all selected ProBuilderMesh edges.
|
||||
/// </summary>
|
||||
public static int totalEdgeCount { get { CacheElementCounts(); return s_TotalEdgeCount; } }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sum of all selected ProBuilder compiled mesh triangles (3 indices make up a triangle, or 4 indices if topology is quad).
|
||||
/// </summary>
|
||||
public static int totalTriangleCountCompiled { get { CacheElementCounts(); return s_TotalTriangleCountCompiled; } }
|
||||
|
||||
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;
|
||||
|
||||
if (t.TryGetComponent<ProBuilderMesh>(out var mesh))
|
||||
Selection.activeObject = mesh;
|
||||
else
|
||||
Selection.activeObject = t;
|
||||
|
||||
Selection.objects = temp;
|
||||
OnObjectSelectionChanged();
|
||||
}
|
||||
|
||||
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 = 0; i < temp.Length; i++)
|
||||
{
|
||||
if (i != ind)
|
||||
temp[i] = Selection.objects[i];
|
||||
}
|
||||
|
||||
Selection.objects = temp;
|
||||
|
||||
if (Selection.activeGameObject == t)
|
||||
Selection.activeObject = temp.FirstOrDefault();
|
||||
|
||||
OnObjectSelectionChanged();
|
||||
}
|
||||
|
||||
internal static void MakeActiveObject(GameObject t)
|
||||
{
|
||||
if (t == null || !Selection.objects.Contains(t))
|
||||
return;
|
||||
|
||||
int ind = System.Array.IndexOf(Selection.objects, t);
|
||||
int len = Selection.objects.Length;
|
||||
|
||||
Object[] temp = new Object[len];
|
||||
|
||||
for (int i = 0; i < len - 1 ; i++)
|
||||
{
|
||||
if(i == ind)
|
||||
{
|
||||
temp[i] = Selection.objects[len - 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
temp[i] = Selection.objects[i];
|
||||
}
|
||||
}
|
||||
|
||||
temp[len - 1] = t;
|
||||
|
||||
Selection.activeObject = t;
|
||||
Selection.objects = temp;
|
||||
OnObjectSelectionChanged();
|
||||
}
|
||||
|
||||
internal static void RemoveMeshFromSelectionInternal(ProBuilderMesh mesh)
|
||||
{
|
||||
if (s_TopSelection.Contains(mesh))
|
||||
s_TopSelection.Remove(mesh);
|
||||
}
|
||||
|
||||
internal static void RefreshSelectionAfterComponentReset(ProBuilderMesh mesh)
|
||||
{
|
||||
ProBuilderEditor.Refresh(true);
|
||||
}
|
||||
|
||||
internal static void SetSelection(IList<GameObject> 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;
|
||||
}
|
||||
|
||||
OnObjectSelectionChanged();
|
||||
}
|
||||
|
||||
internal static void SetSelection(GameObject go)
|
||||
{
|
||||
UndoUtility.RecordSelection(topInternal.ToArray(), "Change Selection");
|
||||
ClearElementAndObjectSelection();
|
||||
AddToSelection(go);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all selected mesh attributes in the current selection. This means triangles, faces, and edges, but not objects.
|
||||
/// </summary>
|
||||
/// <seealso cref="ClearElementAndObjectSelection"/>
|
||||
public static void ClearElementSelection()
|
||||
{
|
||||
UndoUtility.RecordSelection(topInternal.ToArray(), "Clear Selection");
|
||||
if (ProBuilderEditor.instance != null)
|
||||
ProBuilderEditor.instance.ClearElementSelection();
|
||||
InvalidateCaches();
|
||||
if (objectSelectionChanged != null)
|
||||
objectSelectionChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears both the <see cref="UnityEditor.Selection.objects"/> and ProBuilder mesh attribute selections.
|
||||
/// </summary>
|
||||
/// <seealso cref="ClearElementSelection"/>
|
||||
public static void ClearElementAndObjectSelection()
|
||||
{
|
||||
if (ProBuilderEditor.instance != null)
|
||||
ProBuilderEditor.instance.ClearElementSelection();
|
||||
Selection.objects = new Object[0];
|
||||
}
|
||||
|
||||
internal static Vector3 GetHandlePosition()
|
||||
{
|
||||
var active = GetActiveSelectionGroup();
|
||||
|
||||
if(active == null || active.mesh == null)
|
||||
return Vector3.zero;
|
||||
|
||||
switch (VertexManipulationTool.pivotPoint)
|
||||
{
|
||||
case PivotPoint.ActiveElement:
|
||||
case PivotPoint.IndividualOrigins:
|
||||
if (active.elementGroups.Count == 0)
|
||||
goto case default;
|
||||
return active.elementGroups.Last().position;
|
||||
|
||||
case PivotPoint.Center:
|
||||
default:
|
||||
return bounds.center;
|
||||
}
|
||||
}
|
||||
|
||||
internal static Quaternion GetHandleRotation()
|
||||
{
|
||||
var active = GetActiveSelectionGroup();
|
||||
|
||||
if(active == null || active.mesh == null)
|
||||
return Quaternion.identity;
|
||||
|
||||
switch (VertexManipulationTool.handleOrientation)
|
||||
{
|
||||
case HandleOrientation.ActiveObject:
|
||||
return active.mesh.transform.rotation;
|
||||
|
||||
case HandleOrientation.ActiveElement:
|
||||
if (active.elementGroups.Count == 0)
|
||||
goto case HandleOrientation.ActiveObject;
|
||||
return active.elementGroups.Last().rotation;
|
||||
|
||||
default:
|
||||
return Quaternion.identity;
|
||||
}
|
||||
}
|
||||
|
||||
internal static MeshAndElementSelection GetActiveSelectionGroup()
|
||||
{
|
||||
// If the active GameObject does not have a PB Mesh, return another mesh in the selection to get a valid handle position
|
||||
var mesh = activeMesh;
|
||||
if (mesh == null)
|
||||
{
|
||||
foreach (var go in Selection.gameObjects)
|
||||
{
|
||||
if (go.TryGetComponent<ProBuilderMesh>(out var pbmesh))
|
||||
{
|
||||
mesh = pbmesh;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var pair in elementSelection)
|
||||
{
|
||||
if (pair.mesh == mesh)
|
||||
return pair;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bec5fadb27a8bd249a3d1051e2e8f970
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,96 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.ProBuilder;
|
||||
|
||||
namespace UnityEditor.ProBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// A mesh, material and optional transform matrix combination.
|
||||
/// </summary>
|
||||
sealed class Model
|
||||
{
|
||||
// The name of this model.
|
||||
public string name;
|
||||
|
||||
// vertices
|
||||
public Vertex[] vertices;
|
||||
|
||||
// Submeshes
|
||||
public Submesh[] submeshes;
|
||||
|
||||
public Material[] materials;
|
||||
|
||||
// Optional transform matrix.
|
||||
public Matrix4x4 matrix;
|
||||
|
||||
/// <summary>
|
||||
/// Vertex count for the mesh (corresponds to vertices length).
|
||||
/// </summary>
|
||||
public int vertexCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return vertices == null ? 0 : vertices.Length;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Submesh count.
|
||||
/// </summary>
|
||||
public int submeshCount { get { return submeshes.Length; } }
|
||||
|
||||
public Model()
|
||||
{}
|
||||
|
||||
public Model(string name, Mesh mesh, Material material) : this(name, mesh, new Material[] { material }, Matrix4x4.identity)
|
||||
{}
|
||||
|
||||
public Model(string name, Mesh mesh, Material[] materials, Matrix4x4 matrix)
|
||||
{
|
||||
this.name = name;
|
||||
this.vertices = mesh.GetVertices();
|
||||
this.matrix = matrix;
|
||||
this.submeshes = new Submesh[mesh.subMeshCount];
|
||||
this.materials = new Material[mesh.subMeshCount];
|
||||
int matCount = materials != null ? materials.Length : 0;
|
||||
|
||||
for (int submeshIndex = 0; submeshIndex < mesh.subMeshCount; submeshIndex++)
|
||||
{
|
||||
submeshes[submeshIndex] = new Submesh(mesh, submeshIndex);
|
||||
|
||||
if (matCount < 1)
|
||||
materials[submeshIndex] = BuiltinMaterials.defaultMaterial;
|
||||
else
|
||||
this.materials[submeshIndex] = materials[Math.Clamp(submeshIndex, 0, matCount - 1)];
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a pb_Model from a pb_Object, optionally converting to quad topology.
|
||||
/// </summary>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="mesh"></param>
|
||||
/// <param name="quads"></param>
|
||||
public Model(string name, ProBuilderMesh mesh, bool quads = true)
|
||||
{
|
||||
mesh.ToMesh(quads ? MeshTopology.Quads : MeshTopology.Triangles);
|
||||
mesh.Refresh(RefreshMask.UV | RefreshMask.Colors | RefreshMask.Normals | RefreshMask.Tangents);
|
||||
this.name = name;
|
||||
vertices = mesh.GetVertices();
|
||||
submeshes = Submesh.GetSubmeshes(mesh.facesInternal, MaterialUtility.GetMaterialCount(mesh.renderer), quads ? MeshTopology.Quads : MeshTopology.Triangles);
|
||||
materials = new Material[submeshCount];
|
||||
|
||||
for(int i = 0; i < submeshCount; ++i)
|
||||
{
|
||||
materials[i] = mesh.renderer.sharedMaterials[Math.Clamp(i, 0, materials.Length - 1)];
|
||||
if(materials[i] == null)
|
||||
materials[i] = BuiltinMaterials.defaultMaterial;
|
||||
}
|
||||
|
||||
|
||||
matrix = mesh.transform.localToWorldMatrix;
|
||||
mesh.ToMesh(MeshTopology.Triangles);
|
||||
mesh.Refresh();
|
||||
mesh.Optimize();
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user