How to undo and redo actions in babylon js?

Hi there
Is it possible to create a command that undo or redo actions?
I’m trying to create walls and if I press ctrl+z ou ctrl+y the action would move to previous or next state
Moreover, I would like to select only specific angles to draw the walls if I press shift button. Can you help me with this?

Here is the playground https://playground.babylonjs.com/#VHW8N9#1

There is nothing built-in to babylonjs that I’m aware of that provides that functionality. I’ve done some examples with that in the past using a library called ‘redux’. It has a feature called time travel, but to keep babylon in sync with your PG which is adding walls then you probably just need 2 queues to perform compensating actions.

Take for example a rotate and a shift. They need to be undone in the same order or you will not end up in the same state.

For only specific angles to draw walls, you might get what you need by looking at the gizmo rotation snapDistance (it uses radians):
PlaneRotationGizmo | Babylon.js Documentation (babylonjs.com)

1 Like

Can you show some examples of this ‘redux’?

I already use gizmo rotation as an extra tool for fixing the position, but the starting of the next wall becomes unattached to the previous wall. In this way, the app becomes less user-friendly. The snap should come from my function

I suppose that the parts to be modified in the script is this one

var secondClickPos = new BABYLON.Vector2(

                              pickResult.pickedPoint.x,

                              pickResult.pickedPoint.z

                            );

                            var middlePos = new BABYLON.Vector2(

                              (secondClickPos.x - firstClickPos.x) /2 + firstClickPos.x,

                              (secondClickPos.y - firstClickPos.y) /2 + firstClickPos.y

                            );

                            myWall.position.x = middlePos.x;

                            myWall.position.z = middlePos.y;

                            var distance = Math.sqrt(

                              Math.pow(secondClickPos.x - firstClickPos.x, 2) +

                              Math.pow(secondClickPos.y - firstClickPos.y, 2)

                            );

                            myWall.scaling.x = distance+0.15;

                            var angle =Math.atan2(

                              secondClickPos.x - firstClickPos.x,

                              secondClickPos.y - firstClickPos.y

                            ) - Math.PI/2;

                            myWall.rotation.y = angle;

                           

I don’t think you need redux (the reducer syntax is very simple though - it receives an action and returns the new state), because you only need to store a history for undo/redo. Here is code for a generic queue:

export default class Queue<T> {
    _store: T[] = [];

    /**
     * Add an item to the queue.
     * @param val
     */
    enqueue(val: T) {
      this._store.push(val);
    }

    /**
     * Returns undefined if the queue is empty.
     */
    dequeue(): T | undefined {
      return this._store.shift();
    }

    isEmpty(): boolean {
        return this._store.length === 0;
    }
  }

Whenever you do an action put it on the queue for actions that can be undone (and clear the other queue). Then to undo/redo just move between the queues. You’ll need to handle putting the state accordingly.

My experience with memento pattern is from EventStore (write only event storage) and I used it to not need to replay all of the events to have a start state for efficiency purposes. From my perspective it would not be helpful for undo/redo in Babylonj.js terms, because you would need to reload that entire component (rebuild scene). If you went that way though it would be, I think, similar to the 2 queues, except you need to store state to rebuild everything.

Is it possible to show an example of this queue in the playground on any other playground that can do simpler actions? It is the first time for me to deal with such functionality

Yes, exactly how the 2 queues would work together. If you want a “redo” then when you pop from the list of history that you would enqueue to the second queue.

// from above example:
var command = commands.pop();
// then you need to put that onto the redo list:
redo.enqueue({
  command,
  position,
  etc
});

You would need to store enough information in whatever you are putting on the queue to “redo” that action, but for undo then you need to store enough information, so that could be the mesh you need to dispose, etc.

Whenever you do a new action, it would go on the undo list and the redo list would be cleared.

1 Like