- Home /
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[i].x *= factor.x;
copyVertices[i].y *= factor.y;
copyVertices[i].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);
}
}
}
Answer by IgorAherne · Oct 12, 2016 at 09:02 PM
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
I tested @MediaGiant's code and it doesn't seem to work at all without MyUndoRedoCallback.
After looking around on unity forums, it seems that Undo.RecordObject doesn't work for a sharedMesh.
I still don't know how or why this works the way it's setup, though.
Answer by VarunMhatre · May 27, 2021 at 12:28 PM
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;
}
Your answer
Follow this Question
Related Questions
more than one mesh 1 Answer
Shared Meshes, do they auto-update? 0 Answers
Is it possible to make vertices defined by a mesh and mesh filters animating? 0 Answers
Particle systems: Defining the vertices of an EDGE-type emitter 2 Answers
combining meshes 0 Answers