Top.Mail.Ru

Qt и QML. mtPaint3D. Создаем утилиту для 3D-рисования. День 6.

Рад снова всех приветствовать 🤝 Сегодняшний план заключается в добавлении нескольких небольших, но от этого не менее важных деталей.

Здесь я решил поместить небольшую врезку-спойлер с финальным результатом. Пока здесь будет пусто (update: результат добавлен), но по окончанию проекта я размещу результаты работы. И в некоторых из следующих статей "марафона" продублирую аналогичным образом. Итак:

Qt и QML, 3D painting.

И начинать будем с реализации управления камерой на графической сцене. То есть сейчас камера всегда стационарна (я не беру в расчет изменения между Top View и Front View по кнопкам). Соответственно, нужно осуществить возможность перемещения, удаления/приближения и вращения сцены при некоторых производимых мышью действиях 👍 В QML есть готовые варианты данного функционала, например, OrbitCameraController. Его интерфейс позволяет следующее:

Input Action
Left mouse button While the left mouse button is pressed, mouse movement along x-axis moves the camera left and right and movement along y-axis moves it up and down.
Right mouse button While the right mouse button is pressed, mouse movement along x-axis pans the camera around the camera view center and movement along y-axis tilts it around the camera view center.
Both left and right mouse button While both the left and the right mouse button are pressed, mouse movement along y-axis zooms the camera in and out without changing the view center.
Arrow keys Move the camera vertically and horizontally relative to camera viewport.
Page up and page down keys Move the camera forwards and backwards.
Shift key Changes the behavior of the up and down arrow keys to zoom the camera in and out without changing the view center. The other movement keys are disabled.
Alt key Changes the behovior of the arrow keys to pan and tilt the camera around the view center. Disables the page up and page down keys.

Но мне как-то не на 100% по душе описанное в таблице, поэтому буду делать свой вариант:

Input Action
Left mouse button No action.
Right mouse button While the right mouse button is pressed, mouse movement along x-axis pans the camera around the camera view center and movement along y-axis tilts it around the camera view center.
Both left and right mouse button While both the left and the right mouse button are pressed, mouse movements change camera vertical and horizontal position.
Mouse wheel Scrolling the mouse wheel performs zooming in and out.

В результате нехитрых манипуляций в качестве итогового результата имеем PaintCameraController.qml:

import Qt3D.Core 2.15
import Qt3D.Render 2.15
import Qt3D.Input 2.15

Entity{
    id: rootEntity

    property Camera camera;
    property real linearSpeed: 15
    property real lookSpeed: 1000
    property real zoomLimit: 1
    property real dt: 0.001

    MouseDevice {
        id: mouseDevice
    }

    MouseHandler {
        id: mouseHandler

        property point previousPosition
        readonly property vector3d upVector: Qt.vector3d(0.0, 0.0, 1.0)
        property real pan
        property real tilt

        sourceDevice: mouseDevice

        onPanChanged: rootEntity.camera.panAboutViewCenter(pan, upVector);
        onTiltChanged: rootEntity.camera.tiltAboutViewCenter(tilt);

        onWheel: zoom(wheel.angleDelta.y * linearSpeed * dt);
        onPressed: previousPosition = Qt.point(mouse.x, mouse.y);

        onPositionChanged: {
            if (mouse.buttons === Qt.RightButton) {
                pan = -(mouse.x - previousPosition.x) * lookSpeed * dt;
                tilt = (mouse.y - previousPosition.y) * lookSpeed * dt;
            } else {
                if (mouse.buttons === (Qt.LeftButton | Qt.RightButton)) {
                    var dx = -(mouse.x - previousPosition.x) * linearSpeed * dt;
                    var dy = (mouse.y - previousPosition.y) * linearSpeed * dt;

                    camera.translate(Qt.vector3d(dx, dy, 0));
                }
            }

            previousPosition = Qt.point(mouse.x, mouse.y)
        }

        function zoom(value) {
            var zoomVector = camera.viewCenter.minus(camera.position);
            var zoomDistance = zoomVector.length();

            if ((value <= 0) || (zoomDistance > zoomLimit)) {
                camera.translate(Qt.vector3d(0, 0, value), Camera.DontTranslateViewCenter);
            }
        }
    }
}

Интегрируем его в main.qml после объявления объекта Camera:

Camera {
	id: camera

	projectionType: CameraLens.PerspectiveProjection
	fieldOfView: 45
	nearPlane : 0.1
	farPlane : 100
	aspectRatio: 1
	position: Qt.vector3d(0.0, -25.0, 25.0)
	upVector: Qt.vector3d(0.0, 0.0, 1.0)
	viewCenter: Qt.vector3d(0.0, 0.0, 0.0)
}

PaintCameraController {
	camera: camera
	lookSpeed: 35
}

И результат не заставляет себя ждать:

С первой небольшой частью закончили. Вторая заключается в том, что на мой вкус не хватает опять же некой обратной связи при перемещении курсора по базовой плоскости. Сразу покажу, что я в итоге сделал:

Мелочь, но приятно ) Добавился небольшой объект Entity, который перемещается в соответствии с перемещением курсора мыши. Механизм точно такой же как и ранее - создается Entity и к нему добавляются необходимые компоненты, которые определяют его свойства и возможности:

Entity {
	id: basePlaneHoverEntity
	enabled: true
	property var zCoordinate: 0.01

	PlaneMesh {
		id: basePlaneHoverMesh
		width: 0.3
		height: 0.3
	}

	Transform {
		id: basePlaneHoverTransform
		translation: Qt.vector3d(0, 0, baseCuboid.zExtent + basePlaneHoverEntity.zCoordinate)
		rotationX: 90
	}

	GoochMaterial {
		id: basePlaneHoverMaterial
		alpha: 1
		cool: "#0000b0"
	}

	components: [
		basePlaneHoverMesh,
		basePlaneHoverMaterial,
		basePlaneHoverTransform
	]
}

Положение задается при помощи компонента Transform в событии onMoved в baseCuboidPicker:

ObjectPicker {
	id: baseCuboidPicker
	hoverEnabled: true
	dragEnabled: true
	enabled: true
	property bool isAddingPaintObject: false
	property bool paintEnabled: true

	onPressed: {
		if (paintEnabled === true) {
			if (pick.buttons === Qt.LeftButton) {
				isAddingPaintObject = true;
				paintEntity.onBasePlaneMousePressed(pick.worldIntersection);
			}
		}
	}

	onMoved: {
		var hoverCoordinate = Qt.vector3d(pick.worldIntersection.x, pick.worldIntersection.y, basePlaneHoverTransform.translation.z);
		basePlaneHoverTransform.translation = hoverCoordinate;

		if (paintEnabled === true) {
			if (isAddingPaintObject === true) {
				paintEntity.onBasePlaneMouseMoved(pick.worldIntersection);
			}
		}
	}

	onReleased: {
		isAddingPaintObject = false;
	}
}

Результат мы уже видели, так что на сегодня на этом все, осуществили две небольшие, но немаловажные функции. Полный набор исходников, как обычно на том же самом месте )

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Qt3D.Core 2.15
import QtQuick3D 1.15
import QtQuick.Scene3D 2.15
import Qt3D.Render 2.15
import Qt3D.Input 2.15
import Qt3D.Extras 2.15
import QtQuick3D.Materials 1.15
import "../qml/objects"

import microtechnics.paintEntity 1.0
import microtechnics.paintObject 1.0



ApplicationWindow {
    id: window
    width: Screen.width * 3 / 4
    height: Screen.height * 3 / 4
    visible: true
    title: qsTr("mtPaint3D")
    color: "#505050"

    onClosing: {
        paintEntity.destroy();
    }

    GridLayout {
        id: mainLayout
        rows: 12
        columns: 6
        flow: GridLayout.TopToBottom
        anchors.fill: parent

        component GridRectangle : Rectangle {
            Layout.fillHeight: true
            Layout.fillWidth: true
            Layout.preferredWidth: Layout.columnSpan
            Layout.preferredHeight: Layout.rowSpan

            color: "#808080"
            border.color: Qt.darker(color)
            border.width: 1
        }

        GridRectangle {
            id: objectBlock

            Layout.row: 0
            Layout.column: 0
            Layout.rowSpan: 12
            Layout.columnSpan: 1

            ToolPanel {
                id: toolPanel
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: parent.top

                onSelectedToolChanged: {
                    if ((toolPanel.selectedTool === PaintEntity.ToolType.Select) ||
                        (toolPanel.selectedTool === PaintEntity.ToolType.Delete)) {
                        baseCuboidPicker.paintEnabled = false;
                    } else {
                        baseCuboidPicker.paintEnabled = true;
                    }

                    paintEntity.activeTool = toolPanel.selectedTool;
                }
            }

            ColorPanel {
                id: colorPanel
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: toolPanel.bottom
                anchors.topMargin: 10
                visible: true

                onSelectedColorChanged: {
                    paintEntity.setActiveColor(selectedColor);
                }
            }

            TransformPanel {
                id: transformPanel
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: colorPanel.bottom
                anchors.topMargin: 10

                visible: false

                positionLimits: Qt.vector3d(baseCuboid.xExtent, baseCuboid.yExtent, 20)

                onAngleChanged: {
                    if (paintEntity.activePaintObject !== null) {
                        paintEntity.activePaintObject.rotate(angle);
                    }
                }

                onPositionChanged: {
                    if (paintEntity.activePaintObject !== null) {
                        paintEntity.activePaintObject.move(position);
                    }
                }
            }

            Item {
                id: objectsPanels
                anchors.left: parent.left
                anchors.right: parent.right
                anchors.top: transformPanel.bottom

                property var currentHeight;

                CommonObjectPanel {
                    id: commonObjectPanel
                    anchors.left: parent.left
                    anchors.right: parent.right
                    visible: false

                    onParametersChanged: {
                        if (paintEntity.activePaintObject !== null) {
                            paintEntity.activePaintObject.update(parameters);
                        }
                    }
                }
            }
        }

        GridRectangle {
            id: viewBlock

            Layout.row: 0
            Layout.column: 1
            Layout.rowSpan: 1
            Layout.columnSpan: 5

            Row {
                id: viewButtonsRow
                spacing: 2
                anchors.horizontalCenter: parent.horizontalCenter
                anchors.verticalCenter: parent.verticalCenter

                Button {
                    id: topViewButton
                    text: qsTr("Top View")

                    onClicked:
                        ParallelAnimation {
                            PropertyAnimation { target: camera; property: "position"; to: Qt.vector3d(0.0, 0.0, 35.0); duration: 675; }
                            PropertyAnimation { target: camera; property: "upVector"; to: Qt.vector3d(0.0, 1.0, 0.0); duration: 675; }
                    }
                }

                Button {
                    id: frontViewButton
                    text: qsTr("Front View")

                    onClicked:
                        ParallelAnimation {
                            PropertyAnimation { target: camera; property: "position"; to: Qt.vector3d(0.0, -25.0, 25.0); duration: 675; }
                            PropertyAnimation { target: camera; property: "upVector"; to: Qt.vector3d(0.0, 0.0, 1.0); duration: 675; }
                    }
                }
            }
        }

        GridRectangle {
            id: paintBlock

            Layout.row: 1
            Layout.column: 1
            Layout.rowSpan: 11
            Layout.columnSpan: 5

            Scene3D {
                id: scene3D
                anchors.fill: parent
                focus: true
                aspects: ["input", "logic"]

                Entity {
                    id: rootEntity

                    Entity {
                        id: lightEntity
                        components: [
                          PointLight {
                              color: "#ffffa7"
                              intensity: 0.5
                              enabled: true
                          },
                          Transform {
                              translation: Qt.vector3d(10, 0, 10)
                          }
                        ]
                    }

                    Camera {
                        id: camera

                        projectionType: CameraLens.PerspectiveProjection
                        fieldOfView: 45
                        nearPlane : 0.1
                        farPlane : 100
                        aspectRatio: 1
                        position: Qt.vector3d(0.0, -25.0, 25.0)
                        upVector: Qt.vector3d(0.0, 0.0, 1.0)
                        viewCenter: Qt.vector3d(0.0, 0.0, 0.0)
                    }

                    PaintCameraController {
                        camera: camera
                        lookSpeed: 35
                    }

                    RenderSettings {
                        id: renderSettings
                        activeFrameGraph: ForwardRenderer {
                            camera: camera
                            clearColor: "transparent"
                        }
                        pickingSettings.pickMethod: PickingSettings.TrianglePicking
                    }

                    InputSettings { id: inputSettings }

                    Entity {
                        id: baseCuboidEntity

                        CuboidMesh {
                            id: baseCuboid

                            xExtent: 35
                            yExtent: 25
                            zExtent: basePlaneZExtent
                        }

                        Transform {
                            id: baseCuboidTransform
                        }

                        DiffuseSpecularMaterial {
                            id: baseCuboidMaterial
                            ambient: "#ffffff"
                            shininess: 100
                        }

                        ObjectPicker {
                            id: baseCuboidPicker
                            hoverEnabled: true
                            dragEnabled: true
                            enabled: true
                            property bool isAddingPaintObject: false
                            property bool paintEnabled: true

                            onPressed: {
                                if (paintEnabled === true) {
                                    if (pick.buttons === Qt.LeftButton) {
                                        isAddingPaintObject = true;
                                        paintEntity.onBasePlaneMousePressed(pick.worldIntersection);
                                    }
                                }
                            }

                            onMoved: {
                                var hoverCoordinate = Qt.vector3d(pick.worldIntersection.x, pick.worldIntersection.y, basePlaneHoverTransform.translation.z);
                                basePlaneHoverTransform.translation = hoverCoordinate;

                                if (paintEnabled === true) {
                                    if (isAddingPaintObject === true) {
                                        paintEntity.onBasePlaneMouseMoved(pick.worldIntersection);
                                    }
                                }
                            }

                            onReleased: {
                                isAddingPaintObject = false;
                            }
                        }

                        components: [
                            baseCuboid,
                            baseCuboidMaterial,
                            baseCuboidTransform,
                            baseCuboidPicker
                        ]
                    }

                    Entity {
                        id: basePlaneHoverEntity
                        enabled: true
                        property var zCoordinate: 0.01

                        PlaneMesh {
                            id: basePlaneHoverMesh
                            width: 0.3
                            height: 0.3
                        }

                        Transform {
                            id: basePlaneHoverTransform
                            translation: Qt.vector3d(0, 0, baseCuboid.zExtent + basePlaneHoverEntity.zCoordinate)
                            rotationX: 90
                        }

                        GoochMaterial {
                            id: basePlaneHoverMaterial
                            alpha: 1
                            cool: "#0000b0"
                        }

                        components: [
                            basePlaneHoverMesh,
                            basePlaneHoverMaterial,
                            basePlaneHoverTransform
                        ]
                    }

                    PaintEntity {
                        id: paintEntity

                        function hideObjectControls() {
                            for (var i = 0; i < objectsPanels.visibleChildren.length; i++) {
                                objectsPanels.visibleChildren[i].visible = false;
                            }

                            transformPanel.visible = false;
                            commonObjectPanel.visible = false;
                        }

                        onActivePaintObjectChanged: {
                            hideObjectControls();

                            if (activePaintObject !== null) {
                                colorPanel.setCurrentItem(activePaintObject.color);

                                transformPanel.initialPosition = activePaintObject.position;
                                transformPanel.reset();
                                transformPanel.visible = true;

                                switch(activePaintObject.type) {
                                    case PaintObject.Type.Cuboid:
                                        commonObjectPanel.controlsModel = [
                                                {text: qsTr("X extent"), sliderMin: 0.1, sliderMax: baseCuboid.xExtent, sliderCurrent: activePaintObject.parameters[0]},
                                                {text: qsTr("Y extent"), sliderMin: 0.1, sliderMax: baseCuboid.yExtent, sliderCurrent: activePaintObject.parameters[1]},
                                                {text: qsTr("Z extent"), sliderMin: 0.1, sliderMax: 10, sliderCurrent: activePaintObject.parameters[2]}
                                            ];

                                        commonObjectPanel.visible = true;
                                        break;

                                    default:
                                        break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

Исходный код и проект: mtPaint3D_day_6

Подписаться
Уведомление о
guest
0 комментариев
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x