Parisa.H.R
Parisa.H.R

Reputation: 3893

How to create Undo/Redo operations in Qt3D?

I created some entities using qt3d in QML. For example, this code shows a Scene3D element that declares RootEntity which is another QML element that contains the scene graph:

Scene3D
{
    id : scene3d
    anchors.fill: parent
    focus: true
    aspects: ["render", "logic", "input"]
    hoverEnabled: true
    cameraAspectRatioMode: Scene3D.AutomaticAspectRatio


    antialiasing: true

    RootEntity
    {
        id:root
    }

}

RootEntity.qml:

Entity {
id:root

property double x : 0.0


Camera {
    id: mainCamera
    projectionType: CameraLens.PerspectiveProjection
    fieldOfView: 45
    aspectRatio: 16/9
    nearPlane : 0.1
    farPlane : 1000.0
    position: Qt.vector3d(0.0, 4.49373, -3.78577)
    upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
    viewCenter: Qt.vector3d(0.0, 0.5, 0.0)

}

OrbitCameraController
{
    id: mainCameraController
    camera: mainCamera
}

components: [
    RenderSettings {

        Viewport {
            normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
            RenderSurfaceSelector {
                CameraSelector {
                    id: cameraSelector
                    camera: mainCamera
                    FrustumCulling {
                        ClearBuffers {
                            buffers: ClearBuffers.AllBuffers
                            clearColor: "#444449"
                            NoDraw {}
                        }
                        LayerFilter {
                            filterMode: LayerFilter.DiscardAnyMatchingLayers
                            layers: [topLayer]
                        }
                        LayerFilter {
                            filterMode: LayerFilter.AcceptAnyMatchingLayers
                            layers: [topLayer]
                            ClearBuffers {
                                buffers: ClearBuffers.DepthBuffer
                            }
                        }
                    }
                }
            }
        }      
    },
    InputSettings {}
]

Layer {
    id: topLayer
    recursive: true
}

ListModel {
    id: entityModel
    ListElement { x:0;y:0;z:0 }
}

NodeInstantiator
{
    id:instance

    model: entityModel

    delegate: Entity {
        id: sphereEntity
        components: [
            SphereMesh
            {
                id:sphereMesh
                radius: 0.3
            },

            PhongMaterial
            {
                id: materialSphere
                ambient:"red"
            },

            Transform {
                id: transform
                translation:Qt.vector3d(x, y, z)
            }
        ]
    }
}

MouseDevice
{
    id: mouseDev
}

MouseHandler
{
    id: mouseHandler
    sourceDevice: mouseDev

    onPressed:
    {
        x++;
        entityModel.append({"x":x,"y":0.0,"z": Math.random()})
    }
}
}

Output screenshot

When the mouse is clicked in my Scene3D, one sphere is displayed.

I don't know how to delete a specific Entity or create undo/redo effect by hitting Ctrl+Z and Ctrl+Shift+Z in Qt3d. Thanks.

Upvotes: 0

Views: 409

Answers (1)

karlphillip
karlphillip

Reputation: 93468

One approach is to maintain a global list of Qt.vector3d elements and use it to record the position of the spheres that are removed with the "Undo" operation:

  • When the user hits CTRL+Z, create a new Qt.vector3d object to store the position of the last sphere rendered (that is, the one that was last appended to entityModel) and add that position to the global list of 3d vectors;
  • Then, to remove a sphere from the screen, call entityModel.remove() with the index of the sphere that needs to be erased;

The "Redo" operation simply does the opposite:

  • When the user hits CTRL+Y, the last element of the global list of 3d vectors holds the location of the lastest sphere removed: append this position to entityModel so the sphere can be rendered again;
  • Then, remember to erase this position from the global list so the next Undo operation can render a different sphere;

RootEntity.qml:

import QtQuick 2.0

import QtQml.Models 2.15

import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Extras 2.12
import Qt3D.Input 2.12

Entity {
    id: root

    // global list of Qt.vector3d elements that store the location of the spheres that are removed
    property variant removedSpheres : []

    // x-coordinate of the next sphere that will be added
    property double x : 0.0

    Camera {
        id: mainCamera
        projectionType: CameraLens.PerspectiveProjection
        fieldOfView: 45
        aspectRatio: 16/9
        nearPlane : 0.1
        farPlane : 1000.0
        position: Qt.vector3d(0.0, 4.49373, -3.78577)
        upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
        viewCenter: Qt.vector3d(0.0, 0.5, 0.0)
    }

    OrbitCameraController {
        id: mainCameraController
        camera: mainCamera
    }

    components: [
        RenderSettings {

            Viewport {
                normalizedRect: Qt.rect(0.0, 0.0, 1.0, 1.0)
                RenderSurfaceSelector {
                    CameraSelector {
                        id: cameraSelector
                        camera: mainCamera
                        FrustumCulling {
                            ClearBuffers {
                                buffers: ClearBuffers.AllBuffers
                                clearColor: "#444449"
                                NoDraw {}
                            }
                            LayerFilter {
                                filterMode: LayerFilter.DiscardAnyMatchingLayers
                                layers: [topLayer]
                            }
                            LayerFilter {
                                filterMode: LayerFilter.AcceptAnyMatchingLayers
                                layers: [topLayer]
                                ClearBuffers {
                                    buffers: ClearBuffers.DepthBuffer
                                }
                            }
                        }
                    }
                }
            }
        },
        InputSettings {}
    ]

    Layer {
        id: topLayer
        recursive: true
    }

    ListModel {
        id: entityModel

        ListElement { x: 0; y: 0; z: 0 }
    }

    NodeInstantiator {
        id: instance

        model: entityModel

        delegate: Entity {
            id: sphereEntity

            components: [
                SphereMesh { id:sphereMesh; radius: 0.3 },

                PhongMaterial { id: materialSphere; ambient:"red" },

                Transform { id: transform; translation:Qt.vector3d(x, y, z) }
            ]
        }
    }

    MouseDevice {
        id: mouseDev
    }

    MouseHandler {
        id: mouseHandler
        sourceDevice: mouseDev

        onPressed:
        {
            if (mouse.button === Qt.LeftButton)
            {
                console.log("LeftButton: new sphere")

                // add new sphere
                entityModel.append( {"x" : ++root.x, "y" : 0.0, "z" : Math.random()} )
            }

            if (mouse.button === Qt.MiddleButton)
            {
                console.log("MiddleButton: clear spheres")

                // removes all spheres (can't be undone)
                root.x = 0;
                entityModel.clear();
                removedSpheres.length = 0;
            }
        }
    }

    KeyboardDevice {
        id: keyboardDev
    }

    KeyboardHandler {
        id: keyboardHandler
        sourceDevice: keyboardDev
        focus: true

        onPressed: {
            // handle CTRL+Z: undo
            if (event.key === Qt.Key_Z && (event.modifiers & Qt.ControlModifier))
            {
                console.log("CTRL+Z")

                // remove the last sphere added to the screen
                let lastIdx = entityModel.count - 1;
                if (lastIdx >= 0)
                {
                    // save sphere position before removal
                    removedSpheres.push(Qt.vector3d(entityModel.get(lastIdx).x, entityModel.get(lastIdx).y, entityModel.get(lastIdx).z));

                    // remove sphere from the model
                    entityModel.remove(lastIdx);
                }
            }

            // handle CTRL+Y: redo
            if (event.key === Qt.Key_Y && (event.modifiers & Qt.ControlModifier))
            {
                console.log("CTRL+Y")

                // add the last sphere removed back into the model
                if (removedSpheres.length > 0)
                {
                    // add the sphere
                    let lastIdx = removedSpheres.length - 1;
                    entityModel.append( {"x" : removedSpheres[lastIdx].x, "y" : removedSpheres[lastIdx].y, "z" : removedSpheres[lastIdx].z} )

                    // erase the last item added to removedSpheres
                    removedSpheres.pop()
                }
            }
        }
    }

}

Upvotes: 3

Related Questions