Рад снова всех приветствовать 🤝 Сегодняшний план заключается в добавлении нескольких небольших, но от этого не менее важных деталей.
Здесь я решил поместить небольшую врезку-спойлер с финальным результатом. Пока здесь будет пусто (update: результат добавлен), но по окончанию проекта я размещу результаты работы. И в некоторых из следующих статей "марафона" продублирую аналогичным образом. Итак:
И начинать будем с реализации управления камерой на графической сцене. То есть сейчас камера всегда стационарна (я не беру в расчет изменения между 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