About Undo/Redo

Hi
I have an experience about Undo/Redo.

  1. Ready Undo/Redo library.
    Download at https://github.com/ArthurClemens/Javascript-Undo-Manager
    You must import the library.
    var editorHistory = new UndoManager();
    // camera, scene, renderer must be created before.

  2. Get current focused object using raycaster.

    var raycaster = new THREE.Raycaster();
    container.onmousedown = function(e) { // container is html element includes canvas.
            e.preventDefault();
            var mouse = new THREE.Vector2();
            mouse.x = 2 * (e.offsetX / container.clientWidth) - 1;
            mouse.y = 1 - 2 * ( e.offsetY / container.clientHeight );
            raycaster.setFromCamera( mouse, camera );
            var intersects = raycaster.intersectObjects( scene.childrens, true); // true: for picking group object like fbx, obj.
            if ( intersects.length > 0 ) {
                var obj = intersects[0].object;
                if(obj.parent.type != 'Scene') { // It is model or group...
                    obj = obj.parent;
                }
                if( fousedObject  != obj) {
                    fousedObject  = obj;
                    transControl.attach(fousedObject  ); // Add transformController, so, now we can capture any event about from focusedObject.
                }
            } else {
                return;
            }
        };
    
    
    
  3. Capture event when some object’s properties like position, scale, rotation are changed.
    Assume, you are using TransformControls()!

     var oldObjData = null;
     var newObjData = null;
     transControl.addEventListener( 'mouseDown', function(e) {
            oldObjData =getObjectData(focusedObj);
      } );
      transControl.addEventListener( 'mouseUp', function(e) {
             newObjData = getObjectData(focusedObj);
      } );
      transControl.addEventListener( 'dragging-changed', function ( e ) {
             if(e.value === false) { // End dragging
                 addHistory(oldObjData, newObjData); // Store undo/redo
             }
      } );
    
    
  4. getObjectData, addHistory.

    function getObjectData(obj) {
      var data = {
            uuid: obj.uuid, // !Important, used in addHistory.
            position: copyObj({x: obj.position.x, y: obj.position.y, z: obj.position.z}),
            rotation: copyObj({x: obj.rotation.x, y: obj.rotation.y, z: obj.rotation.z}),
            scale: copyObj({x: obj.scale.x, y: obj.scale.y, z: obj.scale.z}),
            opacity: Number(obj.userData.opacity),
        };
        return data;
    }
    
    function addHistory(oldObjData , newObjData ) {
      if(oldObjData && newObjData && oldObjData.uuid == newObjData.uuid) {
            editorHistory.add({
                undo: function() {
                    resetObject(oldObjData);
                },
                redo: function() {
                    resetObject(newObjData);
                }
            });
        }
    }
    
    
    
  5. When fire undo/redo event, let use the history.
    Assume requested Undo … then we can call editorHistory.undo();

editorHistory.undo(); // then will call resetObject(oldObjData)

  1. resetObject
function resetObject(data) {
      var nowObj = // you can find object by data.uuid.
      nowObj.position.x = data.position.x;
      nowObj.position.y = data.position.y;
      nowObj.position.z = data.position.z;
      nowObj.rotation.x = data.rotation.x;
      nowObj.rotation.y = data.rotation.y;
      nowObj.rotation.z = data.rotation.z;
      nowObj.scale.x = data.scale.x;
      nowObj.scale.y = data.scale.y;
      nowObj.scale.z = data.scale.z;
  }

Done!
Thanks for your time.
Niao

Tip: When providing such a resource, it’s a good idea to properly format the code. You can do this by using three ` characters when starting and ending a code listing. E.g.

console.log( 'Hello world!' );

This will make your post much easier to read.

1 Like

Dear Mugen
Thanks for your advise.
I have fixed my post.
Regards.

1 Like