QtQuick3D中模型拾取鼠标动作,点击模型进入对应的页面

滕星纬
2023-12-01

一、设置MouseArea

QtQuick3D自带了拾取鼠标动作的函数,写在鼠标区域MouseArea{}中,鼠标区域填充View3D区域。

//main.qml
View3D {
    id: view3D

    //...设置背景、灯光、相机、放置模型等

    MouseArea{
        anchors.fill: parent
        onClicked: {  //触发点击事件
        var result = view3D.pick(mouse.x, mouse.y)
        //继续操作
    }

PickResult pick(float x, float y)

This method will "shoot" a ray into the scene from view coordinates x and y and return information about the nearest intersection with an object in the scene.
该方法将从视图坐标x和y向场景“发射”光线,并返回与场景中对象最近相交的信息。
This can, for instance, be called with mouse coordinates to find the object under the mouse cursor.
例如,可以使用鼠标坐标调用该命令,以查找鼠标光标下的对象。

该函数的两个参数正是2维坐标下的鼠标在窗口中的两个坐标,使用时传入(mouse.x,mouse.y)即可。其返回值类型是PickResult,PickResult对象属性如下:

distance : float  //距离
normal : vector3d
objectHit : Model  //点击到的模型
position : vector3d  //目标模型位置
sceneNormal : vector3d
scenePosition : vector3d
uvPosition : vector2d

使用pick函数时,必须手动将模型的pickable属性设置为ture(默认为false,不可被选中),同时建议设置模型的objectName属性(QString)用来标识pick的对象。

使用var声明一个变量,用来接收pick的返回值,然后就可用此变量调用objectHit函数,获得点击到的模型进行想要的操作。

var result = view3D.pick(mouse.x, mouse.y)

此时,可以将result.objectHit进行判断是否点击到了模型。若点击到了,可以var一个变量,用来接收模型,进行模型的操作。

if(result.objectHit){//单击处存在对象
    var selectModel = result.objectHit;//定义模型
    if(selectModel.objectName === "car"){//car是某一模型的objectName
        openCarPage.show();//打开car对应的操作界面
        console.log("mouse click car");
    }
    else if(selectModel.objectName === "sphere"){//sphere是某一模型的objectName
        openSpherePage.show();//打开sphere对应的操作界面
        console.log("mouse click sphere");
    }
}
else{//单击处没有对象
    console.log("mouse click other area");
}

到此,模型可以拾取鼠标动作,并根据每个模型的点击进行对应操作。但是想要跳转到对应页面,还需准备好新页面。

二、准备QML文件

  1. 新建QML文件

在Qt中新建两个QML File,分别命名为CarPage.qml和SpherePage.qml。

  1. 在CMakeList.txt中添加两个新文件

如果Build System使用的是CMake,则这一步是必须的。如果用qMake编译器,则不用此步骤,但可能需要相应的其他操作。

在qt_add_qml_module{...}的QML_FILES后,添加CarPage.qml SpherePage.qml

qt_add_qml_module(apptest_MouseAreaOnModel
    URI test_MouseAreaOnModel
    VERSION 1.0
    QML_FILES main.qml Car.qml CarPage.qml SpherePage.qml
    RESOURCES meshes/car.mesh
)
  1. QML代码中

在View3D中写如下代码

//main.qml
CarPage{
    id:openCarPage
}
SpherePage{
    id:openSpherePage
}

4.完善两个字页面的代码

//CarPage.qml
import QtQuick
import QtQuick.Controls 2.15

Window {
    id: secondPage
    width: 640
    height: 480
    title: qsTr("Second Page")

    Rectangle{//一个红色的矩形,填充整个窗口
        anchors.fill: parent
        color: "red"
    }
    Button {//一个实现返回功能的按钮,放在窗口中心
        anchors.centerIn: parent
        width: 60
        height: 40
        text: qsTr("返回")
        onClicked: {
            secondPage.close()
        }
    }

    Text {//在窗口的中间偏上,打印一句话
        anchors.top: parent.top
        anchors.topMargin: 40
        anchors.horizontalCenter: parent.horizontalCenter
        text: "这是小车的子页面"
        font.pixelSize: 20
    }
}
//SpherePage
import QtQuick
import QtQuick.Controls 2.15

Window {
    id: window_sphere
    width: 640
    height: 480
    title: "Sphere Page"

    Rectangle{//一个红色的矩形,填充整个页面
        anchors.fill: parent
        border.color: "red"
        border.width: 10

        Label{
            anchors.centerIn: parent
            font.pixelSize: 50
            text: qsTr("这是球体的子页面")
        }

        MouseArea {//点击页面任何区域都能返回
            anchors.fill: parent
            onClicked: {
                window_sphere.close()
            }
        }
    }
}

到此,已经可以实现所需功能。加一个附加功能:使模型在场景中旋转起来,同时能够满足上述功能(所谓模型旋转,其实是相机进行旋转)。

三、模型旋转

在main.qml的View3D{...}中,将camera利用Node进行封装,实现旋转功能。

//main.qml的View3D中
//设置旋转相机
Node {
    id: cameraNode

    PerspectiveCamera {//透视相机提供了场景的真实投影,其中远处的对象被视为较小。
        id: camera
        y: 70
        z: 300
    }

//    OrthographicCamera{//正投影相机
//        z: 300
//    }

    NumberAnimation {//设置本Node的动画
        id: cameraAnimation
        target: cameraNode
        property: "eulerRotation.y"//动画的该变量是围绕y轴旋转的角度
        duration: 5000
        from: 0
        to: 360
        loops: Animation.Infinite
        running: true
    }
}

四、总结

至此,所有功能均已完成:

  • 场景中放置两个3D模型,其中一个是外部自定义的模型

  • 两个模型同时进行旋转

  • 两个模型分别接受鼠标动作

  • 点击模型后进入对应模型指定的页面

五、不足

未能实现鼠标控制相机进行移动、缩放等功能的同时,还能让模型接收鼠标动作并做出进入下级页面的反应。尝试代码如下:

//实现鼠标控制镜头,但是会覆盖掉MouseArea
import QtQuick3D.Helpers

OrbitCameraController {
    camera: camera
    origin: node1
    xInvert: true
    yInvert: false
}

将此段代码注释之后,除了对模型的旋转有些冲突之外,点击模型已经没有进入次级页面的反应,可以认为是OrbitCameraController挡住了MouseArea。

未尝试的解决办法:

  • 不用OrbitCameraController。在两个模型下方放置一个3D模型,作为托盘或者平台。此模型也可接收鼠标动作,在鼠标按住此模型并进行拖动的时候,此模型带动上边的小车、球体等一同进行旋转,同时也可接收滚轮(wheel)的操作,进行相机的远近变换实现模型在视角中的放缩。

  • http://t.csdn.cn/2OrOo使用此文章中的方法一,利用TabHander类代替MouseAre进行鼠标获取,与MouseArea不同的是,前者接收鼠标动作之后,继续向下方传递鼠标动作,不遮挡底层的模型获取鼠标动作。

六、所有代码

子页面的代码在上文已经给出,本处给出main.qml文件的全部代码。

其中模型组件的添加在之前的文章已经提到过,不再赘述。《利用Qt Quick 3D将Blender中导出的模型资源添加至Quick3D项目中》https://blog.csdn.net/qq_20756957/article/details/129355533

//main.qml
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("Hello World")

    View3D {
        id: view3D
        anchors.fill: parent

        environment: SceneEnvironment {
            clearColor: "#222222"
            backgroundMode: SceneEnvironment.Color
        }


        DirectionalLight {//默认是从z轴正向投向负向
            eulerRotation.x: -90
            //eulerRotation.y: 80

            castsShadow: true
            //castsShadow: true
            //brightness: 1.0
            //worldDirection: Qt.vector3d(-100,-100,-1)
        }

        //模型组件
        Node {
            id: node1
            Model {
                id: mycar
                objectName: "car"
                source: "meshes/car.mesh"
                scale: Qt.vector3d(20, 20, 20)
                pickable: true
                materials: [DefaultMaterial{ diffuseColor: "blue"; }]
            }
            Model {
                id: mysphere
                objectName: "sphere"
                position: Qt.vector3d(0,100,0)
                source: "#Sphere"
                scale: Qt.vector3d(0.5, 0.5, 0.5)
                pickable: true
                materials: PrincipledMaterial {
                    baseColor: "#40c060"
                    roughness: 0.1 // make specular highlight visible
                }
            }
        }

        //添加鼠标区域
        MouseArea{
            anchors.fill: parent
            onClicked: {  //触发点击事件
                //console.log(mouse.x,mouse.y)
                var result = view3D.pick(mouse.x, mouse.y)//该方法将从视图坐标x和y向场景“发射”光线,并返回与场景中对象最近相交的信息。例如,可以使用鼠标坐标调用该命令,以查找鼠标光标下的对象。
                //console.log(result.position)
                if(result.objectHit){//单击处存在对象//此属性保存拾取命中的模型对象。
                    var selectModel = result.objectHit;
                    //console.log("mouse click on model",selectModel.id);
                    if(selectModel.objectName === "car"){
                        openCarPage.show();
                        console.log("mouse click car");
                    }
                    else if(selectModel.objectName === "sphere"){
                        openSpherePage.show();
                        console.log("mouse click sphere");
                    }
                }
                else{
                    console.log("mouse click other area");
                }


            }
        }

        //根据点击的模型做出对应的动作
        function modelClicked(selectModel){
            //待实现
        }

        CarPage{
            id:openCarPage
        }
        SpherePage{
            id:openSpherePage
        }

//        //显示Item
//        Component{
//            id:openItemPanel  //面板
//            ItemPage{
//                width: 300
//                height: 200
//                anchors.centerIn: parent
//            }
//        }
//        Loader{
//            id:myloader
//        }

        //设置旋转相机
        Node {
            id: cameraNode

            PerspectiveCamera {
                id: camera
                y: 70
                z: 300
            }

            NumberAnimation {
                id: cameraAnimation
                target: cameraNode
                property: "eulerRotation.y"
                duration: 5000
                from: 0
                to: 360
                loops: Animation.Infinite
                running: true
            }
        }
//        PerspectiveCamera {//透视相机提供了场景的真实投影,其中远处的对象被视为较小。
//            id: camera
//            y: 70
//            z: 300
//        }
//        OrthographicCamera{//正投影相机
//            z: 300
//        }


//        //实现鼠标控制镜头   import QtQuick3D.Helpers
//        //会覆盖掉MouseArea
//        OrbitCameraController {
//            camera: camera
//            origin: node1
//            xInvert: true
//            yInvert: false
//        }
    }
}
 类似资料: