- Home /
Question by
ProtoTerminator · Mar 20, 2017 at 11:07 AM ·
scriptableobjecteditorwindowmeshesundo
Undo issues in EditorWindow
I'm working on a mesh deformation tool, and I've run into a problem where if I put in a check to not run code when it's not needed (line 147 of VertexDeformWindow), it breaks the Undo functionality. Can anyone help me figure out what's going on?
public class VertexDeformWindow : EditorWindow
{
[MenuItem("Window/Vertex Deform")]
public static void ShowWindow()
{
if (window != null)
{
return;
}
window = GetWindow<VertexDeformWindow>();
window.selection = new Texture2D(32, 32, TextureFormat.ARGB32, false);
var pixels = window.selection.GetPixels();
for(int i = 0; i < pixels.Length; i++)
{
pixels[i] = new Color(0, 0, 0, 0.3f);
}
window.selection.SetPixels(pixels);
window.selection.Apply();
window.meshes = CreateInstance<MultiObjectVerts>() as MultiObjectVerts;
window.meshes.softSize = EditorPrefs.GetFloat("softSize", window.meshes.softSize);
window.meshes.showUnselected = EditorPrefs.GetBool("showUnselected", window.meshes.showUnselected);
window.meshes.meshEditType = (EditType)EditorPrefs.GetInt("editType", (int)window.meshes.meshEditType);
window.meshes.type = (FalloffType)EditorPrefs.GetInt("falloffType", (int)window.meshes.type);
Undo.undoRedoPerformed -= UndoDetected;
Undo.undoRedoPerformed += UndoDetected;
}
static bool active = true;
static void UndoDetected()
{
/*if (active != meshes.active)
{
window.Active = meshes.active;
}*/
//meshes.UpdateStrengths();
if (active)
{
window.meshes.UpdateSelectedVerts();
}
}
bool Active
{
get
{
return meshes.active;
}
set
{
active = value;
meshes.active = value;
if (value)
{
if (selection == null)
{
selection = new Texture2D(32, 32, TextureFormat.ARGB32, false);
var pixels = selection.GetPixels();
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = new Color(0, 0, 0, 0.3f);
}
selection.SetPixels(pixels);
selection.Apply();
}
Tools.hidden = true;
meshes.GetMeshVerts();
}
else
{
Tools.hidden = false;
meshes.ReleaseMeshVerts();
}
}
}
[SerializeField]
MultiObjectVerts meshes;
static VertexDeformWindow window;
void OnDisable()
{
EditorPrefs.SetFloat("softSize", meshes.softSize);
EditorPrefs.SetBool("showUnselected", meshes.showUnselected);
EditorPrefs.SetInt("editType", (int) meshes.meshEditType);
EditorPrefs.SetInt("falloffType", (int) meshes.type);
}
// Window has been selected
void OnFocus()
{
// Remove delegate listener if it has previously
// been assigned.
SceneView.onSceneGUIDelegate -= OnSceneGUI;
// Add (or re-add) the delegate.
SceneView.onSceneGUIDelegate += OnSceneGUI;
}
void OnDestroy()
{
// When the window is destroyed, remove the delegate
// so that it will no longer do any drawing.
SceneView.onSceneGUIDelegate -= OnSceneGUI;
Undo.undoRedoPerformed -= UndoDetected;
Active = false;
}
Rect NormalizeRect(Vector2 start, Vector2 end)
{
Rect rect = new Rect(new Vector2(Mathf.Min(start.x, end.x), Mathf.Min(start.y, end.y)), new Vector2(Mathf.Abs(start.x - end.x), Mathf.Abs(start.y - end.y)));
return rect;
}
Vector2 startPos;
Vector2 endPos;
Rect dragRect = Rect.zero;
bool moved = false;
// Draw gizmos here.
void OnSceneGUI(SceneView sceneView)
{
if (window == null || meshes == null)
{
ShowWindow();
}
if (active)
{
meshes.DrawVerts(sceneView);
if (meshes.SelectedVerts > 0)
{
EditorGUI.BeginChangeCheck();
Vector3 pos = meshes.GetTransformGizmoPosition();
Vector3 relativePos = Handles.PositionHandle(pos, Quaternion.identity) - pos;
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(meshes, "changed meshes");
}
if (GUIUtility.hotControl != 0)
{
moved = true;
}
else if (moved)
{
moved = false;
meshes.UpdateStrengths();
}
//if (relativePos != Vector3.zero) // If uncommented, this line causes the Undo to not work!
{
meshes.MoveSelectedVertices(relativePos);
}
}
Event current = Event.current;
if (current.button == 0)
{
Vector3 mousePosition = current.mousePosition;
int controlID = GUIUtility.GetControlID(FocusType.Passive);
if (current.type == EventType.MouseDown)
{
startPos = mousePosition;
dragRect = Rect.zero;
GUIUtility.hotControl = controlID;
current.Use();
}
if (current.type == EventType.MouseDrag)
{
endPos = mousePosition;
dragRect = NormalizeRect(startPos, endPos);
current.Use();
}
if (current.type == EventType.MouseUp)
{
Undo.RecordObject(meshes, "changed meshes");
endPos = mousePosition;
if (endPos == startPos)
{
// normal click
if (current.control || current.shift)
{
meshes.AddToSelectedVerts(endPos);
}
else if (current.alt)
{
meshes.SubtractFromSelectedVerts(endPos);
}
else
{
meshes.SelectVerts(endPos);
}
}
else
{
// drag select
if (current.control || current.shift)
{
meshes.AddToSelectedVerts(dragRect, sceneView.camera);
}
else if (current.alt)
{
meshes.SubtractFromSelectedVerts(dragRect, sceneView.camera);
}
else
{
meshes.SelectVerts(dragRect, sceneView.camera);
}
}
dragRect = Rect.zero;
GUIUtility.hotControl = 0;
current.Use();
}
}
if (dragRect != Rect.zero)
{
Handles.BeginGUI();
GUI.DrawTexture(dragRect, selection);
Handles.EndGUI();
}
}
Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
Undo.FlushUndoRecordObjects();
}
Texture2D selection;
void OnGUI()
{
if (window == null || meshes == null)
{
ShowWindow();
}
Undo.RecordObject(meshes, "changed gizmo size");
GUILayout.Label("Vertex Deform", EditorStyles.boldLabel);
if (!Active)
{
if (Selection.gameObjects.Length == 0)
{
GUILayout.Label("No objects selected. Please select an object with a MeshFilter and Renderer in the scene");
return;
}
foreach (var obj in Selection.gameObjects)
{
if (!obj.GetComponent<MeshFilter>() || !obj.GetComponent<Renderer>())// || PrefabUtility.GetPrefabType(obj) == PrefabType.Prefab)
{
GUILayout.Label("Improper object(s) selected. Please select an object with a MeshFilter and Renderer in the scene");
return;
}
}
if (GUILayout.Button("Activate"))
{
Active = true;
}
meshes.meshEditType = (EditType) EditorGUILayout.EnumPopup("Mesh Edit Type", meshes.meshEditType);
}
else
{
if (GUILayout.Button("Deactivate"))
{
Active = false;
}
meshes.showUnselected = EditorGUILayout.Toggle("Show Unselected Verts", meshes.showUnselected);
meshes.softSize = EditorGUILayout.FloatField("Soft Size: ", meshes.softSize);
if (meshes.softSize < 0)
{
meshes.softSize = 0;
}
else if (meshes.softSize > 0)
{
meshes.type = (FalloffType) EditorGUILayout.EnumPopup("Falloff Type", meshes.type);
}
if (GUI.changed)
{
meshes.UpdateStrengths();
}
}
Undo.CollapseUndoOperations(Undo.GetCurrentGroup());
Undo.FlushUndoRecordObjects();
if (GUI.changed)
{
SceneView.RepaintAll();
}
}
private void OnInspectorUpdate()
{
Repaint();
}
}
...
public enum FalloffType
{
Linear,
Sine
}
public enum EditType
{
Original,
Copy
}
public class MultiObjectVerts : ScriptableObject
{
public static int vertPixels; // how many pixels to draw vertex, more pixels on higher dpi screens
public float softSize = 5;
public bool showUnselected = true;
public FalloffType type = FalloffType.Sine;
public EditType meshEditType = EditType.Copy;
public List<MeshFilter> meshes = new List<MeshFilter>();
public List<Vector3[]> vertices = new List<Vector3[]>();
List<VertIndices> selectedVerts = new List<VertIndices>();
List<VertIndices> softSelectedVerts = new List<VertIndices>();
List<VertIndices> unselectedVerts = new List<VertIndices>();
public List<VertIndices> vertIndices = new List<VertIndices>();
public bool active;
Color vertColor = Color.blue;
public void UpdateSelectedVerts()
{
selectedVerts.Clear();
unselectedVerts.Clear();
foreach(var vert in vertIndices)
{
if (vert.strength == 1)
{
selectedVerts.Add(vert);
}
else if (vert.strength > 0)
{
softSelectedVerts.Add(vert);
}
else
{
unselectedVerts.Add(vert);
}
}
UpdateStrengths();
}
public int SelectedVerts
{
get
{
return selectedVerts.Count;
}
}
public void GetMeshVerts()
{
ReleaseMeshVerts();
vertPixels = Mathf.RoundToInt(Screen.dpi / 25);
if (vertPixels < 1)
{
vertPixels = 1;
}
HashSet<int> meshInstances = new HashSet<int>();
int index = 0;
int vertCounter = 0;
foreach (var obj in Selection.gameObjects)
{
var filter = obj.GetComponent<MeshFilter>();
if (meshEditType == EditType.Copy && (!filter.sharedMesh.name.Contains("(Clone)") || meshInstances.Contains(filter.sharedMesh.GetInstanceID())))
{
filter.sharedMesh = Instantiate(filter.sharedMesh);
}
meshInstances.Add(filter.sharedMesh.GetInstanceID());
meshes.Add(filter);
var vertexes = filter.sharedMesh.vertices;
vertices.Add(vertexes);
for (int i = 0; i < vertexes.Length; i++)
{
var vert = vertexes[i];
bool cnt = false;
// Check if the vertex is in the same position as another vertex in the same mesh.
for (int j = vertCounter; j < vertIndices.Count; j++)
{
var vi = vertIndices[j];
if (vi.position == vert)
{
cnt = true;
vi.indices.Add(i);
break;
}
}
if (!cnt)
{
VertIndices item = new VertIndices();
item.position = vert;
item.worldPosition = obj.transform.TransformPoint(vert);
item.indices.Add(i);
item.index = index;
vertIndices.Add(item);
unselectedVerts.Add(item);
}
}
index++;
vertCounter += vertexes.Length;
}
UpdateRects(SceneView.lastActiveSceneView.camera);
}
public void ReleaseMeshVerts()
{
meshes.Clear();
vertices.Clear();
selectedVerts.Clear();
unselectedVerts.Clear();
vertIndices.Clear();
}
Vector3 cameraStart;
Vector3 cameraPos;
Vector3 camPos;
Quaternion camRot;
Vector2 camScreenSize;
// Update where the verts are drawn on screen.
void UpdateRects(Camera cam)
{
foreach (var vert in vertIndices)
{
Vector2 screenPos = cam.WorldToScreenPoint(vert.worldPosition);
screenPos.y = cam.pixelHeight - screenPos.y;
vert.screenPos = screenPos;
vert.rect = new Rect(screenPos - Vector2.one * (vertPixels / 2), Vector2.one * vertPixels);
}
}
// Draw vertices on screen.
public void DrawVerts(SceneView sceneView)
{
Camera camera;
camera = sceneView.camera;
if (camera.transform.position != camPos || camera.transform.rotation != camRot || camScreenSize != new Vector2(camera.pixelWidth, camera.pixelHeight))
{
UpdateRects(camera);
}
camPos = camera.transform.position;
camRot = camera.transform.rotation;
camScreenSize = new Vector2(camera.pixelWidth, camera.pixelHeight);
Handles.BeginGUI();
foreach (var vert in vertIndices)
{
if (!showUnselected && vert.strength == 0)
{
continue;
}
if (vert.strength == 0)
{
vertColor = Color.black;
}
else if (vert.strength == 1)
{
vertColor = Color.red;
}
else
{
vertColor = Color.Lerp(Color.green, Color.blue, vert.strength);
}
EditorGUI.DrawRect(vert.rect, vertColor);
}
Handles.EndGUI();
}
// Get position of move handle while vertices are selected.
public Vector3 GetTransformGizmoPosition()
{
Vector3 point = Vector3.zero;
foreach(var vert in selectedVerts)
{
point += vert.worldPosition;
}
point /= selectedVerts.Count;
return point;
}
// Remove selected vertices.
public void RemoveAllSelectedVerts()
{
int counter = selectedVerts.Count - 1;
while (counter >= 0)
{
unselectedVerts.Add(selectedVerts[counter]);
selectedVerts[counter].strength = 0;
selectedVerts.RemoveAt(counter);
counter--;
}
}
// Select a vertex by a single mouse click.
public void SelectVerts(Vector2 mousePosition)
{
RemoveAllSelectedVerts();
AddToSelectedVerts(mousePosition);
}
// Select a vertex by a single mouse click while holding shift or ctrl.
public void AddToSelectedVerts(Vector2 mousePosition)
{
for (int i = 0; i < unselectedVerts.Count; i++)
{
var vert = unselectedVerts[i];
if (vert.rect.Contains(mousePosition))
{
selectedVerts.Add(vert);
unselectedVerts.Remove(vert);
i--;
vert.strength = 1;
break;
}
}
for (int i = 0; i < softSelectedVerts.Count; i++)
{
var vert = softSelectedVerts[i];
if (vert.rect.Contains(mousePosition))
{
selectedVerts.Add(vert);
unselectedVerts.Remove(vert);
i--;
vert.strength = 1;
break;
}
}
UpdateStrengths();
}
// Unselect a vertex by a single mouse click while holding alt.
public void SubtractFromSelectedVerts(Vector2 mousePosition)
{
for (int i = 0; i < selectedVerts.Count; i++)
{
var vert = selectedVerts[i];
if (vert.rect.Contains(mousePosition))
{
unselectedVerts.Add(vert);
selectedVerts.Remove(vert);
i--;
vert.strength = 0;
break;
}
}
UpdateStrengths();
}
// Select vertices by mouse drag.
public void SelectVerts(Rect rect, Camera cam)
{
RemoveAllSelectedVerts();
AddToSelectedVerts(rect, cam);
}
// Select vertices by mouse drag while holding shift or ctrl.
public void AddToSelectedVerts(Rect rect, Camera cam)
{
for (int i = 0; i < unselectedVerts.Count; i++)
{
var vert = unselectedVerts[i];
if (rect.Contains(vert.screenPos, true))
{
selectedVerts.Add(vert);
unselectedVerts.Remove(vert);
vert.strength = 1;
i--;
}
}
for (int i = 0; i < softSelectedVerts.Count; i++)
{
var vert = softSelectedVerts[i];
if (rect.Contains(vert.screenPos, true))
{
selectedVerts.Add(vert);
unselectedVerts.Remove(vert);
vert.strength = 1;
i--;
}
}
UpdateStrengths();
}
// Unselect vertices by mouse drag while holding shift or ctrl.
public void SubtractFromSelectedVerts(Rect rect, Camera cam)
{
for (int i = 0; i < selectedVerts.Count; i++)
{
var vert = selectedVerts[i];
if (rect.Contains(vert.screenPos, true))
{
selectedVerts.Remove(vert);
unselectedVerts.Add(vert);
vert.strength = 0;
i--;
}
}
UpdateStrengths();
}
// Update the softness of each vertex.
public void UpdateStrengths()
{
if (softSize <= 0)
{
return;
}
for(int i = 0; i < unselectedVerts.Count; i++)
{
var vert = unselectedVerts[i];
vert.strength = 0;
foreach (var selectedVert in selectedVerts)
{
float distPercent = Vector3.Distance(vert.worldPosition, selectedVert.worldPosition) / softSize;
float newStrength = 1 - distPercent;
if (distPercent < 1 && vert.strength < newStrength)
{
if (type == FalloffType.Sine)
{
newStrength = (Mathf.Cos(Mathf.PI * (newStrength - 1)) + 1) / 2;
}
vert.strength = newStrength;
}
}
if (vert.strength > 0)
{
softSelectedVerts.Add(vert);
unselectedVerts.RemoveAt(i);
i--;
}
}
for (int i = 0; i < softSelectedVerts.Count; i++)
{
var vert = softSelectedVerts[i];
vert.strength = 0;
foreach (var selectedVert in selectedVerts)
{
float distPercent = Vector3.Distance(vert.worldPosition, selectedVert.worldPosition) / softSize;
float newStrength = 1 - distPercent;
if (distPercent < 1 && vert.strength < newStrength)
{
if (type == FalloffType.Sine)
{
newStrength = (Mathf.Cos(Mathf.PI * (newStrength - 1)) + 1) / 2;
}
vert.strength = newStrength;
}
}
if (vert.strength == 0)
{
unselectedVerts.Add(vert);
softSelectedVerts.RemoveAt(i);
i--;
}
}
}
// Move selected vertices by offset. Moves soft selected vertices by a fraction of the offset.
public void MoveSelectedVertices(Vector3 offset)
{
/*if (offset == Vector3.zero)
{
return;
}*/
var fixedOffset = meshes[selectedVerts[0].index].transform.InverseTransformVector(offset);
foreach (var vert in selectedVerts)
{
foreach (var ind in vert.indices)
{
vert.position += fixedOffset * vert.strength;
vert.worldPosition += offset * vert.strength;
vertices[vert.index][ind] = vert.position;
}
Vector2 screenPos = SceneView.lastActiveSceneView.camera.WorldToScreenPoint(vert.worldPosition);
screenPos.y = SceneView.lastActiveSceneView.camera.pixelHeight - screenPos.y;
vert.screenPos = screenPos;
vert.rect = new Rect(screenPos - Vector2.one * (vertPixels / 2), Vector2.one * vertPixels);
}
foreach (var vert in softSelectedVerts)
{
foreach (var ind in vert.indices)
{
vert.position += fixedOffset * vert.strength;
vert.worldPosition += offset * vert.strength;
vertices[vert.index][ind] = vert.position;
}
Vector2 screenPos = SceneView.lastActiveSceneView.camera.WorldToScreenPoint(vert.worldPosition);
screenPos.y = SceneView.lastActiveSceneView.camera.pixelHeight - screenPos.y;
vert.screenPos = screenPos;
vert.rect = new Rect(screenPos - Vector2.one * (vertPixels / 2), Vector2.one * vertPixels);
}
for (int i = 0; i < meshes.Count; i++)
{
meshes[i].sharedMesh.vertices = vertices[i];
}
}
}
// Hold vertex information.
[System.Serializable]
public class VertIndices
{
public Vector2 screenPos;
public Rect rect;
public float strength = 0;
public float distanceToSelected;
public Vector3 position;
public Vector3 worldPosition;
public int index;
public List<int> indices = new List<int>();
}
Comment