Singularity/Library/PackageCache/com.unity.2d.spriteshape@7.0.7/Editor/SpriteShapeControllerEditor.cs

639 lines
28 KiB
C#
Raw Normal View History

2024-05-06 14:45:45 -04:00
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.U2D;
using UnityEditor;
using UnityEditor.U2D.SpriteShapeInternal;
using UnityEditor.U2D.Common;
using UnityEditor.AnimatedValues;
using UnityEditor.U2D.Path;
using UnityEngine.SceneManagement;
namespace UnityEditor.U2D
{
[CustomEditor(typeof(SpriteShapeController))]
[CanEditMultipleObjects]
internal class SpriteShapeControllerEditor : PathComponentEditor<CustomPath>
{
private static class Contents
{
public static readonly GUIContent splineLabel = new GUIContent("Spline");
public static readonly string editSplineLabel = "Edit Spline";
public static readonly GUIContent fillLabel = new GUIContent("Fill");
public static readonly GUIContent colliderLabel = new GUIContent("Collider");
public static readonly GUIContent fillPixelPerUnitLabel = new GUIContent("Pixel Per Unit", "Pixel Per Unit for fill texture.");
public static readonly GUIContent spriteShapeProfile = new GUIContent("Profile", "The SpriteShape Profile to render");
public static readonly GUIContent materialLabel = new GUIContent("Material", "Material to be used by SpriteRenderer");
public static readonly GUIContent colorLabel = new GUIContent("Color", "Rendering color for the Sprite graphic");
public static readonly GUIContent metaDataLabel = new GUIContent("Meta Data", "SpriteShape specific controlpoint data");
public static readonly GUIContent showComponentsLabel = new GUIContent("Show Render Stuff", "Show Renderer Components.");
public static readonly GUIContent[] splineDetailOptions = { new GUIContent("High Quality"), new GUIContent("Medium Quality"), new GUIContent("Low Quality") };
public static readonly GUIContent splineDetail = new GUIContent("Detail", "Tessellation Quality for rendering.");
public static readonly GUIContent openEndedLabel = new GUIContent("Open Ended", "Is the path open ended or closed.");
public static readonly GUIContent adaptiveUVLabel = new GUIContent("Adaptive UV", "Allow Adaptive UV Generation");
public static readonly GUIContent enableTangentsLabel = new GUIContent("Enable Tangents", "Enable Tangents for 2D Lighting.");
public static readonly GUIContent worldUVLabel = new GUIContent("Worldspace UV", "Generate UV for world space.");
public static readonly GUIContent stretchUVLabel = new GUIContent("Stretch UV", "Stretch the Fill UV to full Rect.");
public static readonly GUIContent stretchTilingLabel = new GUIContent("Stretch Tiling", "Stretch Tiling Count.");
public static readonly GUIContent colliderDetail = new GUIContent("Detail", "Tessellation Quality on the collider.");
public static readonly GUIContent cornerThresholdDetail = new GUIContent("Corner Threshold", "Corner angle threshold below which corners wont be placed.");
public static readonly GUIContent colliderOffset = new GUIContent("Offset", "Extrude collider distance.");
public static readonly GUIContent updateColliderLabel = new GUIContent("Update Collider", "Update Collider as you edit SpriteShape");
public static readonly GUIContent optimizeColliderLabel = new GUIContent("Optimize Collider", "Cleanup planar self-intersections and optimize collider points");
public static readonly GUIContent optimizeGeometryLabel = new GUIContent("Optimize Geometry", "Simplify geometry");
public static readonly GUIContent cacheGeometryLabel = new GUIContent("Cache Geometry", "Bake geometry data. This will save geometry data on editor and load it on runtime instead of generating.");
public static readonly GUIContent uTess2DLabel = new GUIContent("Fill Tessellation (C# Job)", "Use C# Jobs to generate Fill Geometry. (Edge geometry always uses C# Jobs)");
}
private SerializedProperty m_SpriteShapeProp;
private SerializedProperty m_SplineDetailProp;
private SerializedProperty m_IsOpenEndedProp;
private SerializedProperty m_AdaptiveUVProp;
private SerializedProperty m_StretchUVProp;
private SerializedProperty m_StretchTilingProp;
private SerializedProperty m_WorldSpaceUVProp;
private SerializedProperty m_FillPixelPerUnitProp;
private SerializedProperty m_CornerAngleThresholdProp;
private SerializedProperty m_ColliderAutoUpdate;
private SerializedProperty m_ColliderDetailProp;
private SerializedProperty m_ColliderOffsetProp;
private SerializedProperty m_OptimizeColliderProp;
private SerializedProperty m_OptimizeGeometryProp;
private SerializedProperty m_EnableTangentsProp;
private SerializedProperty m_GeometryCachedProp;
private SerializedProperty m_UTess2DGeometryProp;
public static SpriteShapeControllerEditor spriteshapeControllerEditor;
private int m_CollidersCount = 0;
private int[] m_QualityValues = new int[] { (int)QualityDetail.High, (int)QualityDetail.Mid, (int)QualityDetail.Low };
readonly AnimBool m_ShowStretchOption = new AnimBool();
readonly AnimBool m_ShowNonStretchOption = new AnimBool();
private struct ShapeSegment
{
public int start;
public int end;
public int angleRange;
};
private struct ShapeAngleRange
{
public float start;
public float end;
public int order;
public int index;
};
int m_SelectedPoint = -1;
int m_SelectedAngleRange = -1;
int m_SpriteShapeHashCode = 0;
int m_SplineHashCode = 0;
List<ShapeSegment> m_ShapeSegments = new List<ShapeSegment>();
SpriteSelector spriteSelector = new SpriteSelector();
private SpriteShapeController m_SpriteShapeController
{
get { return target as SpriteShapeController; }
}
private void OnEnable()
{
if (target == null)
return;
m_SpriteShapeProp = serializedObject.FindProperty("m_SpriteShape");
m_SplineDetailProp = serializedObject.FindProperty("m_SplineDetail");
m_IsOpenEndedProp = serializedObject.FindProperty("m_Spline").FindPropertyRelative("m_IsOpenEnded");
m_AdaptiveUVProp = serializedObject.FindProperty("m_AdaptiveUV");
m_StretchUVProp = serializedObject.FindProperty("m_StretchUV");
m_StretchTilingProp = serializedObject.FindProperty("m_StretchTiling");
m_WorldSpaceUVProp = serializedObject.FindProperty("m_WorldSpaceUV");
m_FillPixelPerUnitProp = serializedObject.FindProperty("m_FillPixelPerUnit");
m_CornerAngleThresholdProp = serializedObject.FindProperty("m_CornerAngleThreshold");
m_ColliderAutoUpdate = serializedObject.FindProperty("m_UpdateCollider");
m_ColliderDetailProp = serializedObject.FindProperty("m_ColliderDetail");
m_ColliderOffsetProp = serializedObject.FindProperty("m_ColliderOffset");
m_OptimizeColliderProp = serializedObject.FindProperty("m_OptimizeCollider");
m_OptimizeGeometryProp = serializedObject.FindProperty("m_OptimizeGeometry");
m_EnableTangentsProp = serializedObject.FindProperty("m_EnableTangents");
m_GeometryCachedProp = serializedObject.FindProperty("m_GeometryCached");
m_UTess2DGeometryProp = serializedObject.FindProperty("m_UTess2D");
m_ShowStretchOption.valueChanged.AddListener(Repaint);
m_ShowStretchOption.value = ShouldShowStretchOption();
m_ShowNonStretchOption.valueChanged.AddListener(Repaint);
m_ShowNonStretchOption.value = !ShouldShowStretchOption();
m_CollidersCount += ((m_SpriteShapeController.edgeCollider != null) ? 1 : 0);
m_CollidersCount += ((m_SpriteShapeController.polygonCollider != null) ? 1 : 0);
spriteshapeControllerEditor = this;
}
private void OnDisable()
{
SpriteShapeUpdateCache.UpdateCache(targets);
spriteshapeControllerEditor = null;
}
private bool OnCollidersAddedOrRemoved()
{
PolygonCollider2D polygonCollider = m_SpriteShapeController.polygonCollider;
EdgeCollider2D edgeCollider = m_SpriteShapeController.edgeCollider;
int collidersCount = 0;
if (polygonCollider != null)
collidersCount = collidersCount + 1;
if (edgeCollider != null)
collidersCount = collidersCount + 1;
if (collidersCount != m_CollidersCount)
{
m_CollidersCount = collidersCount;
return true;
}
return false;
}
public void DrawHeader(GUIContent content)
{
EditorGUILayout.LabelField(content, EditorStyles.boldLabel);
}
private bool ShouldShowStretchOption()
{
return m_StretchUVProp.boolValue;
}
static bool WithinRange(ShapeAngleRange angleRange, float inputAngle)
{
float range = angleRange.end - angleRange.start;
float angle = Mathf.Repeat(inputAngle - angleRange.start, 360f);
angle = (angle == 360.0f) ? 0 : angle;
return (angle >= 0f && angle <= range);
}
static int RangeFromAngle(List<ShapeAngleRange> angleRanges, float angle)
{
foreach (var range in angleRanges)
{
if (WithinRange(range, angle))
return range.index;
}
return -1;
}
private List<ShapeAngleRange> GetAngleRangeSorted(UnityEngine.U2D.SpriteShape ss)
{
List<ShapeAngleRange> angleRanges = new List<ShapeAngleRange>();
int i = 0;
foreach (var angleRange in ss.angleRanges)
{
ShapeAngleRange sar = new ShapeAngleRange() { start = angleRange.start, end = angleRange.end, order = angleRange.order, index = i };
angleRanges.Add(sar);
i++;
}
angleRanges.Sort((a, b) => a.order.CompareTo(b.order));
return angleRanges;
}
private void GenerateSegments(SpriteShapeController sc, List<ShapeAngleRange> angleRanges)
{
var controlPointCount = sc.spline.GetPointCount();
var angleRangeIndices = new int[controlPointCount];
ShapeSegment activeSegment = new ShapeSegment() { start = -1, end = -1, angleRange = -1 };
m_ShapeSegments.Clear();
for (int i = 0; i < controlPointCount; ++i)
{
var actv = i;
var next = SplineUtility.NextIndex(actv, controlPointCount);
var pos1 = sc.spline.GetPosition(actv);
var pos2 = sc.spline.GetPosition(next);
bool continueStrip = (sc.spline.GetTangentMode(actv) == ShapeTangentMode.Continuous), edgeUpdated = false;
float angle = 0;
if (false == continueStrip || activeSegment.start == -1)
angle = SplineUtility.SlopeAngle(pos1, pos2) + 90.0f;
next = (!sc.spline.isOpenEnded && next == 0) ? (actv + 1) : next;
int mn = (actv < next) ? actv : next;
int mx = (actv > next) ? actv : next;
var anglerange = RangeFromAngle(angleRanges, angle);
angleRangeIndices[actv] = anglerange;
if (anglerange == -1)
{
activeSegment = new ShapeSegment() { start = mn, end = mx, angleRange = anglerange };
m_ShapeSegments.Add(activeSegment);
continue;
}
// Check for Segments. Also check if the Segment Start has been resolved. Otherwise simply start with the next one
if (activeSegment.start != -1)
continueStrip = continueStrip && (angleRangeIndices[activeSegment.start] != -1);
bool canContinue = (actv != (controlPointCount - 1)) || (!sc.spline.isOpenEnded && (actv == (controlPointCount - 1)));
if (continueStrip && canContinue)
{
for (int s = 0; s < m_ShapeSegments.Count; ++s)
{
activeSegment = m_ShapeSegments[s];
if (activeSegment.start - mn == 1)
{
edgeUpdated = true;
activeSegment.start = mn;
m_ShapeSegments[s] = activeSegment;
break;
}
if (mx - activeSegment.end == 1)
{
edgeUpdated = true;
activeSegment.end = mx;
m_ShapeSegments[s] = activeSegment;
break;
}
}
}
if (!edgeUpdated)
{
activeSegment.start = mn;
activeSegment.end = mx;
activeSegment.angleRange = anglerange;
m_ShapeSegments.Add(activeSegment);
}
}
}
private int GetAngleRange(SpriteShapeController sc, int point, ref int startPoint)
{
int angleRange = -1;
startPoint = point;
for (int i = 0; i < m_ShapeSegments.Count; ++i)
{
if (point >= m_ShapeSegments[i].start && point < m_ShapeSegments[i].end)
{
angleRange = m_ShapeSegments[i].angleRange;
startPoint = point; // m_ShapeSegments[i].start;
if (angleRange >= sc.spriteShape.angleRanges.Count)
angleRange = 0;
break;
}
}
return angleRange;
}
private void UpdateSegments()
{
var sc = target as SpriteShapeController;
// Either SpriteShape Asset or SpriteShape Data has changed.
if (m_SpriteShapeHashCode != sc.spriteShapeHashCode || m_SplineHashCode != sc.splineHashCode)
{
List<ShapeAngleRange> angleRanges = GetAngleRangeSorted(sc.spriteShape);
GenerateSegments(sc, angleRanges);
m_SpriteShapeHashCode = sc.spriteShapeHashCode;
m_SplineHashCode = sc.splineHashCode;
m_SelectedPoint = -1;
}
}
private int ResolveSpriteIndex(List<int> spriteIndices, ISelection<int> selection, ref List<int> startPoints)
{
var spriteIndexValue = spriteIndices.FirstOrDefault();
var sc = target as SpriteShapeController;
var spline = sc.spline;
if (sc == null || sc.spriteShape == null)
return -1;
UpdateSegments();
if (sc.spriteShape != null)
{
if (selection.Count == 1)
{
m_SelectedAngleRange = GetAngleRange(sc, selection.elements[0], ref m_SelectedPoint);
startPoints.Add(m_SelectedPoint);
spriteIndexValue = spline.GetSpriteIndex(m_SelectedPoint);
}
else
{
m_SelectedAngleRange = -1;
foreach (var index in selection.elements)
{
int startPoint = index;
int angleRange = GetAngleRange(sc, index, ref startPoint);
if (m_SelectedAngleRange != -1 && angleRange != m_SelectedAngleRange)
{
m_SelectedAngleRange = -1;
break;
}
startPoints.Add(startPoint);
m_SelectedAngleRange = angleRange;
}
}
}
if (m_SelectedAngleRange != -1)
spriteSelector.UpdateSprites(sc.spriteShape.angleRanges[m_SelectedAngleRange].sprites.ToArray());
else
spriteIndexValue = -1;
return spriteIndexValue;
}
public int GetAngleRange(int index)
{
int startPoint = 0;
var sc = target as SpriteShapeController;
UpdateSegments();
return GetAngleRange(sc, index, ref startPoint);
}
public override void OnInspectorGUI()
{
var updateCollider = false;
EditorGUI.BeginChangeCheck();
serializedObject.Update();
EditorGUILayout.PropertyField(m_SpriteShapeProp, Contents.spriteShapeProfile);
DoEditButton<SpriteShapeEditorTool>(PathEditorToolContents.icon, Contents.editSplineLabel);
DoPathInspector<SpriteShapeEditorTool>();
var pathTool = SpriteShapeEditorTool.activeSpriteShapeEditorTool;
if (Selection.gameObjects.Length == 1 && pathTool != null)
{
var sc = target as SpriteShapeController;
var path = pathTool.GetPath(sc);
if (path != null)
{
var selection = path.selection;
if (selection.Count > 0)
{
var spline = sc.spline;
var spriteIndices = new List<int>();
List<int> startPoints = new List<int>();
foreach (int index in selection.elements)
spriteIndices.Add(spline.GetSpriteIndex(index));
var spriteIndexValue = ResolveSpriteIndex(spriteIndices, selection, ref startPoints);
if (spriteIndexValue != -1)
{
EditorGUI.BeginChangeCheck();
spriteSelector.ShowGUI(spriteIndexValue);
if (EditorGUI.EndChangeCheck())
{
foreach (var index in startPoints)
{
var data = path.GetData(index);
data.spriteIndex = spriteSelector.selectedIndex;
path.SetData(index, data);
}
pathTool.SetPath(target);
}
}
EditorGUILayout.Space();
}
}
}
DoSnappingInspector<SpriteShapeEditorTool>();
EditorGUILayout.Space();
DrawHeader(Contents.splineLabel);
EditorGUILayout.IntPopup(m_SplineDetailProp, Contents.splineDetailOptions, m_QualityValues, Contents.splineDetail);
serializedObject.ApplyModifiedProperties();
DoOpenEndedInspector<SpriteShapeEditorTool>(m_IsOpenEndedProp);
serializedObject.Update();
EditorGUILayout.PropertyField(m_AdaptiveUVProp, Contents.adaptiveUVLabel);
if (!m_IsOpenEndedProp.boolValue)
EditorGUILayout.PropertyField(m_OptimizeGeometryProp, Contents.optimizeGeometryLabel);
EditorGUILayout.PropertyField(m_EnableTangentsProp, Contents.enableTangentsLabel);
if (UnityEditor.EditorTools.ToolManager.activeToolType == typeof(SpriteShapeEditorTool))
{
// Cache Geometry is only editable for Scene Objects or when in Prefab Isolation Mode.
if (Selection.gameObjects.Length == 1 && Selection.transforms.Contains(Selection.gameObjects[0].transform))
{
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_GeometryCachedProp, Contents.cacheGeometryLabel);
if (EditorGUI.EndChangeCheck())
{
if (m_GeometryCachedProp.boolValue)
{
var geometryCache = m_SpriteShapeController.spriteShapeGeometryCache;
if (!geometryCache)
geometryCache = m_SpriteShapeController.gameObject
.AddComponent<SpriteShapeGeometryCache>();
geometryCache.hideFlags = HideFlags.HideInInspector;
}
else
{
if (m_SpriteShapeController.spriteShapeGeometryCache)
Object.DestroyImmediate(m_SpriteShapeController.spriteShapeGeometryCache);
}
m_SpriteShapeController.RefreshSpriteShape();
}
}
SpriteShapeUpdateCache.s_cacheGeometrySet = true;
}
EditorGUI.BeginChangeCheck();
var threshold = EditorGUILayout.Slider(Contents.cornerThresholdDetail, m_CornerAngleThresholdProp.floatValue, 0.0f, 90.0f);
if (EditorGUI.EndChangeCheck())
{
m_CornerAngleThresholdProp.floatValue = threshold;
updateCollider = true;
}
EditorGUILayout.Space();
DrawHeader(Contents.fillLabel);
EditorGUILayout.PropertyField(m_UTess2DGeometryProp, Contents.uTess2DLabel);
EditorGUILayout.PropertyField(m_StretchUVProp, Contents.stretchUVLabel);
if (ShouldShowStretchOption())
{
EditorGUILayout.PropertyField(m_StretchTilingProp, Contents.stretchTilingLabel);
}
else
{
EditorGUILayout.PropertyField(m_FillPixelPerUnitProp, Contents.fillPixelPerUnitLabel);
EditorGUILayout.PropertyField(m_WorldSpaceUVProp, Contents.worldUVLabel);
}
if (m_SpriteShapeController.gameObject.GetComponent<PolygonCollider2D>() != null || m_SpriteShapeController.gameObject.GetComponent<EdgeCollider2D>() != null)
{
EditorGUILayout.Space();
DrawHeader(Contents.colliderLabel);
EditorGUILayout.PropertyField(m_ColliderAutoUpdate, Contents.updateColliderLabel);
if (m_ColliderAutoUpdate.boolValue)
{
EditorGUILayout.PropertyField(m_ColliderOffsetProp, Contents.colliderOffset);
EditorGUILayout.PropertyField(m_OptimizeColliderProp, Contents.optimizeColliderLabel);
if (m_OptimizeColliderProp.boolValue)
EditorGUILayout.IntPopup(m_ColliderDetailProp, Contents.splineDetailOptions, m_QualityValues, Contents.colliderDetail);
}
}
if (EditorGUI.EndChangeCheck())
{
updateCollider = true;
}
serializedObject.ApplyModifiedProperties();
if (updateCollider || OnCollidersAddedOrRemoved())
BakeCollider();
}
void BakeCollider()
{
if (m_SpriteShapeController.autoUpdateCollider == false)
return;
PolygonCollider2D polygonCollider = m_SpriteShapeController.polygonCollider;
if (polygonCollider)
{
Undo.RegisterCompleteObjectUndo(polygonCollider, Undo.GetCurrentGroupName());
EditorUtility.SetDirty(polygonCollider);
m_SpriteShapeController.RefreshSpriteShape();
}
EdgeCollider2D edgeCollider = m_SpriteShapeController.edgeCollider;
if (edgeCollider)
{
Undo.RegisterCompleteObjectUndo(edgeCollider, Undo.GetCurrentGroupName());
EditorUtility.SetDirty(edgeCollider);
m_SpriteShapeController.RefreshSpriteShape();
}
}
void ShowMaterials(bool show)
{
HideFlags hideFlags = HideFlags.HideInInspector;
if (show)
hideFlags = HideFlags.None;
Material[] materials = m_SpriteShapeController.spriteShapeRenderer.sharedMaterials;
foreach (Material material in materials)
{
material.hideFlags = hideFlags;
EditorUtility.SetDirty(material);
}
}
[DrawGizmo(GizmoType.InSelectionHierarchy)]
static void RenderSpline(SpriteShapeController m_SpriteShapeController, GizmoType gizmoType)
{
if (UnityEditor.EditorTools.ToolManager.activeToolType == typeof(SpriteShapeEditorTool))
return;
var m_Spline = m_SpriteShapeController.spline;
var oldMatrix = Handles.matrix;
var oldColor = Handles.color;
Handles.matrix = m_SpriteShapeController.transform.localToWorldMatrix;
Handles.color = Color.grey;
var pointCount = m_Spline.GetPointCount();
for (var i = 0; i < (m_Spline.isOpenEnded ? pointCount - 1 : pointCount); ++i)
{
Vector3 p1 = m_Spline.GetPosition(i);
Vector3 p2 = m_Spline.GetPosition((i + 1) % pointCount);
var t1 = p1 + m_Spline.GetRightTangent(i);
var t2 = p2 + m_Spline.GetLeftTangent((i + 1) % pointCount);
Vector3[] bezierPoints = Handles.MakeBezierPoints(p1, p2, t1, t2, m_SpriteShapeController.splineDetail);
Handles.DrawAAPolyLine(bezierPoints);
}
Handles.matrix = oldMatrix;
Handles.color = oldColor;
}
}
[UnityEditor.InitializeOnLoad]
internal static class SpriteShapeUpdateCache
{
internal static bool s_cacheGeometrySet = false;
static SpriteShapeUpdateCache()
{
UnityEditor.EditorApplication.playModeStateChanged += change =>
{
if (change == UnityEditor.PlayModeStateChange.ExitingEditMode)
UpdateSpriteShapeCacheInOpenScenes();
};
SceneManagement.EditorSceneManager.sceneSaving += (scene, removingScene) =>
{
SaveSpriteShapesInScene(scene);
};
UnityEditor.EditorTools.ToolManager.activeToolChanging += () =>
{
if (UnityEditor.EditorTools.ToolManager.activeToolType == typeof(SpriteShapeEditorTool))
{
if (null != SpriteShapeControllerEditor.spriteshapeControllerEditor)
UpdateCache(SpriteShapeControllerEditor.spriteshapeControllerEditor.targets);
}
};
}
static void SaveSpriteShapesInScene(Scene scene)
{
var gos = scene.GetRootGameObjects();
foreach (var go in gos)
{
if (!go.activeInHierarchy)
continue;
var scs = go.GetComponentsInChildren<SpriteShapeController>();
foreach (var sc in scs)
{
var jh = sc.BakeMesh();
jh.Complete();
}
}
}
static void UpdateSpriteShapeCacheInOpenScenes()
{
for (int i = 0; s_cacheGeometrySet && (i < SceneManager.sceneCount); ++i)
{
var scene = SceneManager.GetSceneAt(i);
SaveSpriteShapesInScene(scene);
}
s_cacheGeometrySet = false;
}
internal static void UpdateCache(UnityEngine.Object[] targets)
{
foreach (var t in targets)
{
var s = t as SpriteShapeController;
if (s)
if (s.gameObject.activeInHierarchy && s.spriteShapeGeometryCache)
s.spriteShapeGeometryCache.UpdateGeometryCache();
}
}
}
}