using UnityEditor; using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using System.Linq; namespace Obi{ /** * Custom inspector for ObiStitcher component. */ [CustomEditor(typeof(ObiStitcher))] public class ObiStitcherEditor : Editor { ObiStitcher stitcher; static public bool editing = false; static public Vector3 sewingToolHandle1 = Vector3.zero; static public Vector3 sewingToolHandle2 = Vector3.one; static public bool[] selectionStatus = new bool[0]; public void OnEnable(){ stitcher = (ObiStitcher)target; // initialize sewing tool to sensible values: if (stitcher.Actor1 != null && stitcher.Actor2 != null){ sewingToolHandle1 = stitcher.Actor1.transform.position; sewingToolHandle2 = stitcher.Actor2.transform.position; } } public override void OnInspectorGUI() { serializedObject.UpdateIfRequiredOrScript(); EditorGUI.BeginChangeCheck(); ObiActor actor1 = EditorGUILayout.ObjectField("First actor",stitcher.Actor1, typeof(ObiActor),true) as ObiActor; if (EditorGUI.EndChangeCheck()){ Undo.RecordObject(stitcher, "Set first actor"); stitcher.Actor1 = actor1; if (actor1 != null) sewingToolHandle1 = actor1.transform.position; PrefabUtility.RecordPrefabInstancePropertyModifications(stitcher); } EditorGUI.BeginChangeCheck(); ObiActor actor2 = EditorGUILayout.ObjectField("Second actor",stitcher.Actor2, typeof(ObiActor),true) as ObiActor; if (EditorGUI.EndChangeCheck()){ Undo.RecordObject(stitcher, "Set second actor"); stitcher.Actor2 = actor2; if (actor2 != null) sewingToolHandle2 = actor2.transform.position; PrefabUtility.RecordPrefabInstancePropertyModifications(stitcher); } if (stitcher.Actor1 != null && stitcher.Actor2 != null && stitcher.Actor1.solver != stitcher.Actor2.solver){ EditorGUILayout.HelpBox("Both actors must be managed by the same solver.",MessageType.Error); } EditorGUILayout.HelpBox("Stitch count: " + stitcher.StitchCount,MessageType.None); // edit mode: GUI.enabled = stitcher.Actor1 != null && stitcher.Actor2 != null; editing = GUILayout.Toggle(editing,"Edit","LargeButton"); if (editing){ // Clear all stitches if (GUILayout.Button("Clear all stitches")){ if (EditorUtility.DisplayDialog("Clearing stitches","Are you sure you want to remove all stitches?","Ok","Cancel")){ Undo.RecordObject(stitcher, "Clear all stitches"); stitcher.Clear(); PrefabUtility.RecordPrefabInstancePropertyModifications(stitcher); } } // Remove selected stitches if (GUILayout.Button("Remove selected stitches")){ List removedStitches = new List(); for(int i = 0; i < selectionStatus.Length; ++i){ if (selectionStatus[i]){ removedStitches.Add(i); selectionStatus[i] = false; } } if (removedStitches.Count > 0){ Undo.RecordObject(stitcher, "Remove stitches"); // Remove from last to first, to avoid throwing off subsequent indices: foreach(int i in removedStitches.OrderByDescending(i => i)){ stitcher.RemoveStitch(i); } PrefabUtility.RecordPrefabInstancePropertyModifications(stitcher); } } // Add stitch: if (GUILayout.Button("Add Stitch")) { FindClosestParticles(out int particle1, out int particle2); if (particle1 >= 0 && particle2 >= 0) { Undo.RecordObject(stitcher, "Add stitch"); stitcher.AddStitch(particle1, particle2); PrefabUtility.RecordPrefabInstancePropertyModifications(stitcher); } } } GUI.enabled = true; // Apply changes to the serializedProperty if (GUI.changed){ serializedObject.ApplyModifiedProperties(); //stitcher.PushDataToSolver(ParticleData.NONE); } } public void FindClosestParticles(out int particle1, out int particle2) { particle1 = -1; particle2 = -1; float minDistance = float.MaxValue; if (stitcher.Actor1 == null || stitcher.Actor2 == null) return; var handle1 = HandleUtility.WorldToGUIPointWithDepth(sewingToolHandle1); var handle2 = HandleUtility.WorldToGUIPointWithDepth(sewingToolHandle2); if (stitcher.Actor1 == stitcher.Actor2) { float minDistance2 = float.MaxValue; for (int i = 0; i < stitcher.Actor1.activeParticleCount;++i) { Vector3 pos = stitcher.Actor1.GetParticlePosition(stitcher.Actor1.solverIndices[i]); pos = HandleUtility.WorldToGUIPointWithDepth(pos); float distance1 = (pos - handle1).sqrMagnitude; float distance2 = (pos - handle2).sqrMagnitude; if (distance1 < minDistance){ minDistance = distance1; particle1 = i; } if (distance2 < minDistance2){ minDistance2 = distance2; particle2 = i; } } }else{ // find closest particle to each end of the sewing tool: for (int i = 0; i < stitcher.Actor1.activeParticleCount; ++i) { Vector3 pos = stitcher.Actor1.GetParticlePosition(stitcher.Actor1.solverIndices[i]); pos = HandleUtility.WorldToGUIPointWithDepth(pos); float min = (pos - handle1).sqrMagnitude; if (min < minDistance) { minDistance = min; particle1 = i; } } minDistance = float.MaxValue; for (int i = 0; i < stitcher.Actor2.activeParticleCount; ++i) { Vector3 pos = stitcher.Actor2.GetParticlePosition(stitcher.Actor2.solverIndices[i]); pos = HandleUtility.WorldToGUIPointWithDepth(pos); float min = (pos - handle2).sqrMagnitude; if (min < minDistance) { minDistance = min; particle2 = i; } } } } public void DrawSewingTool() { FindClosestParticles(out int particle1, out int particle2); if (particle1 >= 0 && particle2 >= 0) { sewingToolHandle1 = stitcher.Actor1.GetParticlePosition(stitcher.Actor1.solverIndices[particle1]); sewingToolHandle2 = stitcher.Actor2.GetParticlePosition(stitcher.Actor2.solverIndices[particle2]); float radius1 = stitcher.Actor1.GetParticleMaxRadius(stitcher.Actor1.solverIndices[particle1]); float radius2 = stitcher.Actor2.GetParticleMaxRadius(stitcher.Actor2.solverIndices[particle2]); Handles.color = Color.white; #if (UNITY_2022_1_OR_NEWER) sewingToolHandle1 = Handles.FreeMoveHandle(sewingToolHandle1, radius1 * 2, new Vector3(.5f,.5f,.5f),Handles.SphereHandleCap); sewingToolHandle2 = Handles.FreeMoveHandle(sewingToolHandle2, radius2 * 2, new Vector3(.5f,.5f,.5f),Handles.SphereHandleCap); #else sewingToolHandle1 = Handles.FreeMoveHandle(sewingToolHandle1, Quaternion.identity, radius1 * 2, new Vector3(.5f, .5f, .5f), Handles.SphereHandleCap); sewingToolHandle2 = Handles.FreeMoveHandle(sewingToolHandle2, Quaternion.identity, radius2 * 2, new Vector3(.5f, .5f, .5f), Handles.SphereHandleCap); #endif Vector3 direction = Vector3.Normalize(sewingToolHandle2 - sewingToolHandle1); Handles.color = Color.yellow; ObiEditorUtils.DrawArrowHandle(sewingToolHandle1 + direction*(radius1 + 0.05f), sewingToolHandle2 - direction*(radius2+0.05f)); } } /** * Draws selected stitches in the scene view and allows their selection. */ public void OnSceneGUI(){ Array.Resize(ref selectionStatus,stitcher.StitchCount); if (!editing) return; DrawSewingTool(); if (stitcher.Actor1 != null && stitcher.Actor2 != null){ int controlID = GUIUtility.GetControlID("stitcher".GetHashCode(),FocusType.Passive); float distanceToClosest = float.MaxValue; int selectedIndex = -1; int i = 0; foreach(ObiStitcher.Stitch stitch in stitcher.Stitches){ Vector3 pos1 = stitcher.Actor1.GetParticlePosition(stitcher.Actor1.solverIndices[stitch.particleIndex1]); Vector3 pos2 = stitcher.Actor2.GetParticlePosition(stitcher.Actor2.solverIndices[stitch.particleIndex2]); switch (Event.current.GetTypeForControl(controlID)){ case EventType.MouseDown: if (Event.current.button != 0) break; // If the user is pressing shift, accumulate selection. if ((Event.current.modifiers & EventModifiers.Shift) == 0 && (Event.current.modifiers & EventModifiers.Alt) == 0){ for(int j = 0; j < selectionStatus.Length; j++) selectionStatus[j] = false; } float distance = HandleUtility.DistanceToLine(pos1,pos2); if (distance < 10 && distance < distanceToClosest){ distanceToClosest = distance; selectedIndex = i; // Prevent deselection if we have selected a stitch: GUIUtility.hotControl = controlID; Event.current.Use(); } break; case EventType.Repaint: Handles.color = selectionStatus[i]?Color.red:Color.cyan; Handles.DrawDottedLine(pos1,pos2,2); break; } ++i; } if (selectedIndex >= 0){ selectionStatus[selectedIndex] = !selectionStatus[selectedIndex]; } } } } }