Undo/Redo on Meshes - This code works, but how?

From reading many questions asking whether it is possible to perform an undo and redo on Unity meshes the typical answers are no, because they are not serializable, or you need to use the SerializedObject class, derive from the ScriptableObject or MonoBehaviour classes, and many other suggestions like custom mesh classes, etc.

I’ve been after a solution to this that is simple and reliable for a long time and have now come up with a working solution, but, I don’t know how it works. The following code demonstrates a custom editor window that takes a game object and transforms the vertices of the mesh. After clicking apply you can then use ctrl-z and ctrl-y to undo and redo the operation.

The magic is in the Undo.undoRedoPerformed callback called MyUndoRedoCallback. It simply copies the vertices and assigns them straight back to the mesh. It only works when placed in this callback and nowhere else.

I’m going to go ahead and use this as it is and also wanted to share the solution with others, but maybe someone can explain how or why this works.

Thanks.

using UnityEngine;
using UnityEditor;

public class MeshUndoRedo: EditorWindow
{
	[MenuItem("Window/Undo Redo", false, 0)]
	static void ShowTheWindow()
	{
		MeshUndoRedo myWindow = (MeshUndoRedo)EditorWindow.GetWindow(typeof(MeshUndoRedo), false, "Undo Redo");
		myWindow.minSize = new Vector2(200.0f, 150.0f);
	}

	public GameObject theObject = null;
	public Vector3 factor = new Vector3(1f, 1f, 1f);

	void OnFocus()
	{		
		Undo.undoRedoPerformed -= this.MyUndoRedoCallback;
		Undo.undoRedoPerformed += this.MyUndoRedoCallback;
	}
	
	public void MyUndoRedoCallback()
	{
		if (theObject == null)
			return;

		MeshFilter meshFilter = theObject.GetComponent<MeshFilter>();
		UnityEngine.Mesh mesh = meshFilter ? meshFilter.sharedMesh : null;
		
		if (mesh)
		{			
			Vector3[] copyVertices = new Vector3[mesh.vertices.Length];
			copyVertices = mesh.vertices;				
			mesh.vertices = copyVertices;
		}

		SceneView.RepaintAll();
	}

	public void ApplyVertexFactors(Vector3 factor)
	{
		if (theObject == null)
			return;
		
		MeshFilter meshFilter = theObject.GetComponent<MeshFilter>();
		UnityEngine.Mesh mesh = meshFilter ? meshFilter.sharedMesh : null;
		
		Undo.RecordObject(mesh, "Modify Vertices");
		
		if (mesh)
		{			
			Vector3[] copyVertices = new Vector3[mesh.vertices.Length];
			copyVertices = mesh.vertices;
			
			for (int i = 0; i < copyVertices.Length; i++)
			{
				copyVertices_.x *= factor.x;_

copyVertices_.y = factor.y;
copyVertices.z = factor.z;
}
_

* mesh.vertices = copyVertices;*
* }*
* SceneView.RepaintAll(); *
* }*

* public void OnGUI()*
* {*
* GUILayout.Space(5);*

* theObject = (GameObject)EditorGUILayout.ObjectField(theObject, typeof(GameObject), true);*

* GUILayout.Label(“Current Factors”);*

* GUILayout.Label("X: " + factor.x.ToString() + " Y: " + factor.y.ToString() + " Z: " + factor.z.ToString());*

* if (GUILayout.Button(“Expand”, GUILayout.Height(17)))*
* {*
_ factor = 2f;
}*_

* if (GUILayout.Button(“Shrink”, GUILayout.Height(17)))*
* {*
* factor /= 2f;*
* }*

* if (GUILayout.Button(“Apply”, GUILayout.Height(20)))*
* {*
* ApplyVertexFactors(factor);*
* }*
* }*
}

It will work without the callback as well.
Copying of vertices in MyUndoRedoCallback doesn’t do anything useful.
It only repaints quickly after ctr+z was pressed, without us having to manually do something in the scene viewport (to update screen and display changes).

It works because of Undo.RecordObject(mesh, "Modify Vertices"); on line 47, which records the mesh (that comes from UnityEngine, and hence can be used by Undo), prior to its modification

Hi @MediaGiant, thanks for sharing your post, it helped me implement a solution for undoing vertex changes on my meshes.

Your code gets even more bizarre: look at the block Line 30-35

if (mesh)
{            
   Vector3[] copyVertices = new Vector3[mesh.vertices.Length];
   copyVertices = mesh.vertices;                
   mesh.vertices = copyVertices;
}

This section seems to be critical for the Undo/Redo to work at all.
And you can change it to the following and it works the same.

if (mesh)
{
   mesh.vertices = mesh.vertices;
}