(注意,开源版的 Qt Quick 3D 是狗都不用的 GPL 协议)
Qt Creator 中有一个 picking 的示例,用于演示 View3D 中物体的拾取:
在示例基础上,我又加了一个简单的拖动效果,如图所示:
在使用 OpenGL 实现拾取的时候,我们可以用射线法。Qt Quick 3D 中封装了拾取操作,通过 View3D 的 pick 函数,可以取到 View3D 视口某个点下离屏幕最近的那个物体 Model。
PickResult pick(float x, float y)
该函数返回一个 PickResult 对象,通过其 objectHit 属性可以判断是否拾取到了物体。
View3D {
MouseArea {
id: mouse_area
anchors.fill: parent
onPressed: {
//获取点在View上的屏幕坐标
pick_screen.text = "(" + mouse.x + ", " + mouse.y + ")"
//pick取与该点射线路径相交的离最近的Model的信息,返回PickResult对象
var result = control.pick(mouse.x, mouse.y)
//判断objectHit是否有效,就可以只知道是否拾取到了物体
if (result.objectHit) {
} else {
}
}
}
}
在 Qt5.15 中,返回的 PickResult 类型只有简单的几个属性:
//拾取原点与物体之间的距离,用视口坐标拾取则拾取原点就是观察点Camera的位置
distance : float
//拾取选中的Model对象
objectHit : Model
//pick点在场景中的位置,(可能相当于射线与物体表面的焦点坐标)
//This property holds the scene position of the hit.
scenePosition : vector3d
//pick点的UV位置
//This property holds the UV position of the hit.
uvPosition : vector2d
在 Qt6.3 中,View3D 增加了 pickAll 函数拾取该点下所有的物体,同时 PickResult 类型也增加了一些属性:
//局部空间中被选中的面的法线
//This property holds the normal of the face that was hit in local coordinate space.
normal : vector3d
//This property holds the scene position of the hit in local coordinate space.
position : vector3d
//This property holds the normal of the face that was hit in scene coordinate space.
sceneNormal : vector3d
拾取到物体之后,就可以对这个 Model 节点进行操作了。本文 Demo 中我实现了一个简单的拖动操作,主要流程是:
1.pick 选中时保存 Model 位置和 pick 位置的视口屏幕坐标差值(可以通过 View3D 的 mapFrom3DScene 函数将场景坐标转换为视口坐标,使用该函数需要先给 View3D 设置 camera )
2.在鼠标移动的过程中,通过差值还原物体 Model 的相对位置,使用 View3D 的 mapTo3DScene 函数转换得到 Model 新的场景坐标。
(但是这个逻辑没有考虑透视投影时,移动场景坐标 xy,视口坐标 xy 并不是等比变化的)
主要代码
Github(PickModel.qml): https://github.com/gongjianbo/HelloQtQuick3D
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick3D 1.15
import QtQuick3D.Helpers 1.15
View3D {
id: control
//背景
environment: SceneEnvironment {
clearColor: "darkGreen"
backgroundMode: SceneEnvironment.Color
}
//观察相机
//View3D的mapTo/mapFrom坐标转换函数需要先设置camera属性
camera: perspective_camera
PerspectiveCamera {
id: perspective_camera
z: 300
}
//光照
DirectionalLight {
eulerRotation.y: 45
}
//立方体
Model {
id: cube_node
objectName: "Cube"
source: "#Cube"
//使能pick
pickable: true
materials: DefaultMaterial {
diffuseColor: mouse_area.pickNode == cube_node ? "cyan" : "yellow"
}
//立方体转动
SequentialAnimation on eulerRotation {
running: true
loops: Animation.Infinite
PropertyAnimation {
duration: 10000
from: Qt.vector3d(0, 0, 0)
to: Qt.vector3d(360, 360, 360)
}
}
}
//锥体
Model {
id: cone_node
objectName: "Cone"
source: "#Cone"
pickable: true
x: 100
z: 50
materials: DefaultMaterial {
diffuseColor: mouse_area.pickNode == cone_node ? "cyan" : "orange"
}
}
//球体
Model {
id: sphere_node
objectName: "Sphere"
source: "#Sphere"
pickable: true
x: -100
z: -50
materials: DefaultMaterial {
diffuseColor: mouse_area.pickNode == sphere_node ? "cyan" : "purple"
}
}
//展示拾取对象的信息
Row {
x: 20
y: 20
spacing: 10
Column {
Label {
color: "white"
text: "Pick Node:"
}
Label {
color: "white"
text: "Screen Position:"
}
Label {
color: "white"
text: "Distance:"
}
Label {
color: "white"
text: "World Position:"
}
}
Column {
Label {
id: pick_name
color: "white"
}
Label {
id: pick_screen
color: "white"
}
Label {
id: pick_distance
color: "white"
}
Label {
id: pick_word
color: "white"
}
}
}
MouseArea {
id: mouse_area
anchors.fill: parent
hoverEnabled: false
property var pickNode: null
//鼠标和物体xy的偏移
property real xOffset: 0
property real yOffset: 0
property real zOffset: 0
onPressed: {
//获取点在View上的屏幕坐标
pick_screen.text = "(" + mouse.x + ", " + mouse.y + ")"
//pick取与该点射线路径相交的离最近的Model的信息,返回PickResult对象
//因为该模块一直在迭代,新的版本可以从PickResult对象获取更多的信息
//Qt6中还提供了pickAll获取与该射线相交的所有Model信息
var result = control.pick(mouse.x, mouse.y)
//目前只在点击时更新了pick物体的信息
if (result.objectHit) {
pickNode = result.objectHit
pick_name.text = pickNode.objectName
pick_distance.text = result.distance.toFixed(2)
pick_word.text = "("
+ result.scenePosition.x.toFixed(2) + ", "
+ result.scenePosition.y.toFixed(2) + ", "
+ result.scenePosition.z.toFixed(2) + ")"
//console.log('in',pick_screen.text)
//console.log(result.scenePosition)
var map_from = control.mapFrom3DScene(pickNode.scenePosition)
//var map_to = control.mapTo3DScene(Qt.vector3d(mouse.x,mouse.y,map_from.z))
//console.log(map_from)
//console.log(map_to)
xOffset = map_from.x - mouse.x
yOffset = map_from.y - mouse.y
zOffset = map_from.z
} else {
pickNode = null
pick_name.text = "None"
pick_distance.text = " "
pick_word.text = " "
}
}
onPositionChanged: {
if(!mouse_area.containsMouse || !pickNode){
return
}
var pos_temp = Qt.vector3d(mouse.x + xOffset, mouse.y + yOffset, zOffset);
var map_to = control.mapTo3DScene(pos_temp)
pickNode.x = map_to.x
pickNode.y = map_to.y
}
}
}
参考文章来源:https://www.toymoban.com/news/detail-497275.html
Qt文档:https://doc.qt.io/qt-5/qml-qtquick3d-view3d.html文章来源地址https://www.toymoban.com/news/detail-497275.html
到了这里,关于Qt Quick 3D学习:鼠标拾取物体的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!