How to rotate a 3d cube in qml? - qt

I am developing an application in Qt Creator with QtQuick. The purpose of this application is to rotate a cube in 3 dimensions with the help of a finger.
I managed to develop the creation of actions in relation to the finger movements but I am completely stuck on the rotation of the cube in relation to the fingers.
I have tried to create a direction vector with respect to the current and previous points as well as the normal vector. I have applied the Euler rotation in x and y on the cube but at a certain point, the cube does not rotate as expected.
Here is my qml code for the main page of the application :
main.qml
import QtQuick 2.15
import QtQuick.Window 2.14
import QtQuick3D 1.15
import Qt3D.Input 2.0
import QtQuick.Controls 2.1
import Qt3D.Extras 2.15
Window {
visible: true
width: 640
height: 480
title: qsTr("MouseArea Demo")
MouseArea{
property int previousX: -1
property int previousY: -1
anchors.fill : parent
onPressed: {
previousX = mouseX;
previousY = mouseY;
}
onPositionChanged: {
let direction = Qt.vector2d(mouseX - previousX, mouseY - previousY).normalized();
let normal = Qt.vector2d(direction.y, direction.x).normalized();
console.log("direction : " + direction);
console.log("normal : " + normal);
cube.eulerRotation.x += normal.x * 3;
cube.eulerRotation.y += normal.y * 3;
previousX = mouseX;
previousY = mouseY
}
onReleased: {
previousX = -1;
previousY = -1;
}
}
View3D {
id: view
anchors.fill: parent
camera: camera
renderMode: View3D.Overlay
PerspectiveCamera {
id: camera
position: Qt.vector3d(0, 200, 300)
eulerRotation.x: -30
}
DirectionalLight {
eulerRotation.x: -30
}
Model {
id: cube
visible: true
position: Qt.vector3d(0, 0, 0)
source: "#Cube"
materials: [ DefaultMaterial {
diffuseMap: Texture {
id: texture
source: "../build-colorpicker2d-Desktop_Qt_5_15_2_GCC_64bit-Debug/res.png"
}
}
]
}
}
}
here is a video about what I talk to you 2 days ago about the y rotation problem.
Kind regards.

First of all, you should add this in your .pro file :
QT += qml quick 3dcore 3dinput 3dquick 3dlogic 3dquickextras 3dextras
I have 3 class in qml :main.qml , RootEntity.qml and SOrbitCameraController.qml
I have one Scene3D and inside this, I can put all my Entities. I Create a separate class and call it RootEntity.
The more important point of this way is orbitController that I use in my class. this makes that you can rotate cube by mouse and for doing this you need Entity. because of this, I use Scene3D and Entity instead of view3d and models.
In main.qml:
import QtQuick 2.12
import QtQuick.Scene3D 2.12
import QtQuick.Window 2.12
import "."
Window {
visible: true
width: 640
height: 480
Scene3D
{
id : scene3d
anchors.fill: parent
focus: true
aspects: ["render", "logic", "input"]
hoverEnabled: true
cameraAspectRatioMode: Scene3D.AutomaticAspectRatio
antialiasing: true
RootEntity
{
id:root
}
}
}
in RootEntity.qml:
import QtQuick 2.0
import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Extras 2.12
import Qt3D.Input 2.12
import "."
Entity {
id: root
//create camera
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)
}
//use my class instead of OrbitCameraController
SOrbitCameraController {
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 {}
,
ScreenRayCaster
{
id:screenRayCaster
onHitsChanged:
{
drawLineMesh(hits)
}
}
]
Layer {
id: topLayer
recursive: true
}
Entity {
id: cubeEntity
components: [
CuboidMesh
{
xExtent: 1
yExtent: 1
zExtent: 1
}
,
Transform {
id: t
translation: Qt.vector3d(0, 0, 0)
}
,
PhongMaterial
{
ambient: "red"
}
]
}
}
in SOrbitCameraController.qml :
import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
Entity{
id: root
property Camera camera;
property real dt: 0.001
property real linearSpeed: 1
property real lookSpeed: 500
property real zoomLimit: 0.16
MouseDevice {
id: mouseDevice
sensitivity: 0.001 // Make it more smooth
}
MouseHandler {
id: mh
readonly property vector3d upVect: Qt.vector3d(0, 1, 0)
property point lastPos;
property real pan;
property real tilt;
sourceDevice: mouseDevice
onPanChanged: root.camera.panAboutViewCenter(pan, upVect);
onTiltChanged: root.camera.tiltAboutViewCenter(tilt);
onPressed: {
lastPos = Qt.point(mouse.x, mouse.y);
}
onPositionChanged: {
// You can change the button as you like for rotation or translation
if (mouse.buttons === 1){ // Left button for rotation
pan = -(mouse.x - lastPos.x) * dt * lookSpeed;
tilt = (mouse.y - lastPos.y) * dt * lookSpeed;
} else if (mouse.buttons === 2) { // Right button for translate
var rx = -(mouse.x - lastPos.x) * dt * linearSpeed;
var ry = (mouse.y - lastPos.y) * dt * linearSpeed;
camera.translate(Qt.vector3d(rx, ry, 0))
} else if (mouse.buttons === 3) { // Left & Right button for zoom
ry = (mouse.y - lastPos.y) * dt * linearSpeed
zoom(ry)
}
lastPos = Qt.point(mouse.x, mouse.y)
}
onWheel: {
zoom(wheel.angleDelta.y * dt * linearSpeed)
}
function zoom(ry) {
if (ry > 0 && zoomDistance(camera.position, camera.viewCenter) < zoomLimit) {
return
}
camera.translate(Qt.vector3d(0, 0, ry), Camera.DontTranslateViewCenter)
}
function zoomDistance(posFirst, posSecond) {
return posSecond.minus(posFirst).length()
}
}
}
and at the end my main.cpp :
#include <QGuiApplication>
#include <QQmlApplicationEngine>
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
{
return -1;
}
return app.exec();
}
the out put

The Node has a rotate() method. The trick is to do the rotation in Node.SceneSpace. This will ensure that the rotations compound correctly in any axis you choose in that the rotations will be applied on top of the latest orientation. I use a MouseArea to capture 2d movements in either X or Y. In 3d we interpret this as rotations in Y-axis or X-axis respectively. Here's a working example:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick3D
Page {
background: Rectangle { color: "#848895" }
Node {
id: standAloneScene
DirectionalLight { ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0) }
Node {
id: node
Model {
id: model
source: "#Cube"
materials: [
DefaultMaterial { diffuseColor: Qt.rgba(0.053, 0.130, 0.219, 0.75) }
]
}
}
OrthographicCamera {
id: cameraOrthographicFront
y: 500; z: 1000
lookAtNode: node
}
}
View3D {
anchors.fill: parent
importScene: standAloneScene
camera: cameraOrthographicFront
}
MouseArea {
anchors.fill:parent
property real pressedX
property real pressedY
onMouseXChanged: Qt.callLater(update)
onMouseYChanged: Qt.callLater(update)
onPressed: {
[pressedX,pressedY] = [mouseX,mouseY];
}
function update() {
let [dx,dy] = [mouseX - pressedX,mouseY - pressedY];
[pressedX,pressedY] = [mouseX,mouseY];
node.rotate(dx, Qt.vector3d(0, 1, 0), Node.SceneSpace);
node.rotate(dy, Qt.vector3d(1, 0, 0), Node.SceneSpace);
}
}
}
You can Try it Online!

Related

How to add a light to a 3d scene in Qml?

I am developing an application with a cube with 6 different textures in QML.
I made the cube pattern with Blender and attached the texture to the model I created and saved.
But I have a small problem, the textures load correctly but I have shadows that appear and darken the different sides of the cube.
Does anyone have an idea how to remove these shadows?
I put below the code of my application and a screenshot of what I get as a result.
main.qml
import QtQuick 2.12
import QtQuick.Scene3D 2.12
import QtQuick.Window 2.12
Window {
visible: true
width: 640
height: 480
Scene3D
{
id : scene3d
anchors.fill: parent
focus: false
aspects: ["render", "logic", "input"]
hoverEnabled: true
cameraAspectRatioMode: Scene3D.AutomaticAspectRatio
RootEntity
{
id:root
}
}
}
SOrbitCamera.qml
import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
Entity{
id: root
property Camera camera;
property real dt: 0.001
property real linearSpeed: 1
property real lookSpeed: 500
MouseDevice {
id: mouseDevice
sensitivity: 0.007
}
MouseHandler {
id: mh
property vector3d upVect: Qt.vector3d(0, 1, 0)
property point lastPos;
property real pan;
property real tilt;
sourceDevice: mouseDevice
onPanChanged: {
if (camera.position.y < 0.5 && camera.position.y > -3.) {
root.camera.panAboutViewCenter(-pan, upVect);
}
if (camera.position.y > 0.5 && camera.position.y < 3.) {
root.camera.panAboutViewCenter(pan, upVect);
}
if (camera.position.y < 6. && camera.position.y > 3.) {
root.camera.panAboutViewCenter(-pan, upVect);
}
if (camera.position.y > -6. && camera.position.y < -3.) {
root.camera.panAboutViewCenter(pan, upVect);
}
}
onTiltChanged: root.camera.tiltAboutViewCenter(tilt);
onPressed: {
lastPos = Qt.point(mouse.x, mouse.y);
}
onPositionChanged: {
if (mouse.buttons === 1){ // Left button for rotation
pan = -(mouse.x - lastPos.x) * dt * lookSpeed;
tilt = (mouse.y - lastPos.y) * dt * lookSpeed;
console.log(camera.position)
}
lastPos = Qt.point(mouse.x, mouse.y)
}
}
}
RootEntity.qml
import QtQuick 2.0
import QtQuick3D 1.15
import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Extras 2.12
import Qt3D.Input 2.12
Entity {
id: root
//create camera
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: cubeTransform.translation
}
//use my class instead of OrbitCameraController
SOrbitCameraController {
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: "black"
NoDraw {}
}
LayerFilter {
filterMode: LayerFilter.DiscardAnyMatchingLayers
layers: [topLayer]
}
LayerFilter {
filterMode: LayerFilter.AcceptAnyMatchingLayers
layers: [topLayer]
ClearBuffers {
buffers: ClearBuffers.DepthBuffer
}
}
}
}
}
}
},
InputSettings {}
,
ScreenRayCaster
{
id:screenRayCaster
onHitsChanged:
{
drawLineMesh(hits)
}
}
]
Layer {
id: topLayer
recursive: true
}
Entity {
id: cubeEntity
Texture2D {
id:texture
TextureImage {
source: "qrc:/../Downloads/texture.png"
}
}
Mesh {
id: cubeMesh
source: "qrc:/../Downloads/cube.obj"
}
Transform {
id: cubeTransform
translation: Qt.vector3d(0, 0, 0)
}
NormalDiffuseMapMaterial{
id: material
normal: texture
diffuse: texture
specular: texture
}
components: [cubeMesh, cubeTransform, material]
}
}
I add the following code in RootEntity.qml
import QtQuick 2.0
import QtQuick3D 1.15
import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Extras 2.12
import Qt3D.Input 2.12
import QtQuick 2.15
import QtQuick.Window 2.12
Entity {
id: root
Entity {
id: lightEntity
components: [
DirectionalLight{
intensity: 0.5
enabled: true
},
Transform{
translation: Qt.vector3d(0,50,0)
}
]
}
//create camera
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: cubeTransform.translation
}
SOrbitCameraController {
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: "black"
NoDraw {}
}
LayerFilter {
filterMode: LayerFilter.DiscardAnyMatchingLayers
layers: [topLayer]
}
LayerFilter {
filterMode: LayerFilter.AcceptAnyMatchingLayers
layers: [topLayer]
ClearBuffers {
buffers: ClearBuffers.DepthBuffer
}
}
}
}
}
}
},
InputSettings {}
]
Layer {
id: topLayer
recursive: true
}
/*
CuboidMesh{
id: cubeMesh
xExtent: 1
yExtent: 1
zExtent: 1
}*/
Entity {
id: cubeEntity
Texture2D {
id:texture
TextureImage {
source: "qrc:/../Downloads/texture.png"
}
}
Mesh {
id: cubeMesh
source: "qrc:/../Downloads/cube.obj"
}
Transform {
id: cubeTransform
translation: Qt.vector3d(0, 0, 0)
}
NormalDiffuseMapMaterial{
id: material
normal: texture
diffuse: texture
}
components: [cubeMesh, cubeTransform, material]
}
}
I have the following result :
You need to define some light source for your scene.
These are some example for Directional and Point lights:
DirectionalLight {
id: directionalLight
x: -0
y: 828.821
color: "#e0eef0"
shadowMapQuality: Light.ShadowMapQualityMedium
shadowFactor: 58
castsShadow: true
eulerRotation.z: 43.68403
eulerRotation.y: -57.35873
eulerRotation.x: -35.78494
z: 1415.04834
brightness: 80
}
PointLight {
id: lightPoint
x: -7.983
y: 737.434
shadowFactor: 4
castsShadow: true
brightness: 122
quadraticFade: 0.19392
z: 108.31305
}
You can move your lights or rotate the lights direction with changing the x, y and defining rotation values.

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()})
}
}
}
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.
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()
}
}
}
}
}

how to make an updatable model in QML

I'm trying to make a QML widget that is interactive and will update its model.
Directly modifying the model does not work, so I create a updateModel(int index, real x, real y) signal in InteractiveGraph.
However the change done in onUpdateModel produces no effect on the UI.
I also tried with an onUpdateModel handler such as this: { var newModel = model.slice(); newModel[index].x = x; newModel[index].y = y; model = newModel } but the result is even worse as it breaks the self-dragging of the vertex item, and the model is not updated in the UI anyway.
Is the Array class not a good model for this use-case?
Is there some readymade model that is more appropriate?
InteractiveGraph.qml:
import QtQuick 2.0
/* An interactive undirected graph, whose vertices are draggable */
Item {
id: root
property alias model: repeater.model
property real size: 20
signal updateModel(int index, real x, real y)
Canvas { // render lines connecting the vertices
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
ctx.beginPath()
for(var i = 0; i < model.length; i++) {
if(i === 0) ctx.moveTo(model[i].x, model[i].y);
else ctx.lineTo(model[i].x, model[i].y);
}
ctx.closePath()
ctx.stroke()
}
}
Repeater { // instantiate items corresponding to vertices
id: repeater
delegate: Rectangle {
id: self
color: "red"
border.color: "#000"
width: size
height: size
x: modelData.x - size/2
y: modelData.y - size/2
Text { anchors.centerIn: parent; text: index }
MouseArea { // make items self-draggable
anchors.fill: parent
drag.target: self
}
// invert the x and y relation, and send back the change to model
onXChanged: root.updateModel(index, x + size/2, y + size/2)
onYChanged: root.updateModel(index, x + size/2, y + size/2)
}
}
}
main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
InteractiveGraph {
id: interactiveGraph
anchors.fill: parent
model: [
{x: 15, y: 20},
{x: 100, y: 220},
{x: 145, y: 230},
{x: 225, y: 130},
{x: 140, y: 88},
{x: 290, y: 60},
]
onUpdateModel: { model[index].x = x; model[index].y = y; model = model; }
}
}
I should have read Models and Views in Qt Quick, to find out the appropriate model to use is ListModel.
The only caveat is that we can't use x/y property names inside the ListElement child of ListModel, as when using ListElement, the variable modelData is not used anymore, but directly the property name is exposed, and x/y would collide with delegate's properties with the same name.
(In case of name clash, we can use model.fieldName instead of fieldName.)
InteractiveGraph.qml:
Item {
id: root
property alias model: repeater.model
property real size: 20
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
var ctx = getContext("2d")
ctx.fillStyle = "#eee"; ctx.fillRect(0, 0, width, height)
ctx.beginPath()
for(var i = 0; i < model.count; i++) {
var item = model.get(i)
if(i === 0) ctx.moveTo(item.xPos, item.yPos)
else ctx.lineTo(item.xPos, item.yPos)
}
ctx.stroke()
}
}
Repeater {
id: repeater
delegate: Rectangle {
id: self
color: "red"; border.color: "#000"
x: xPos - size/2; y: yPos - size/2; width: size; height: size
onXChanged: canvas.requestPaint(); onYChanged: canvas.requestPaint()
Text { anchors.centerIn: parent; text: index }
MouseArea {
anchors.fill: parent
drag.target: self
onPositionChanged: model.set(index, {xPos: self.x + size/2, yPos: self.y + size/2})
}
}
}
}
main.qml:
import QtQuick 2.12
import QtQuick.Window 2.12
Window {
id: root
visible: true
width: 640
height: 480
title: qsTr("Hello World")
ListModel {
id: graphModel
ListElement {xPos: 15; yPos: 20}
ListElement {xPos: 100; yPos: 220}
ListElement {xPos: 145; yPos: 230}
ListElement {xPos: 225; yPos: 130}
ListElement {xPos: 140; yPos: 88}
ListElement {xPos: 290; yPos: 60}
}
InteractiveGraph {
id: interactiveGraph
anchors.fill: parent
model: graphModel
}
}

QML 3D - change size (expand/reduce) of model rendered in UI to fit in the current window

I am rendering a 3D model using Mesh by reading from a .obj file, and I am trying to change its size dynamically to take the parent window's dimensions. Is there a way to resize the object? Currently when I run the app the model takes roughly half the height and one-third of the width of the main-window, and I am not sure where it picks it up from.
I have tried to use viewportRect in ForwardRenderer but that did not change the display. I was also trying to figure out if zooming with the camera would be possible, but from what I saw in the docs the zoom scale factor needs hardcoded integer values and again I need it to be dynamic.
The current display is like this -
Here is my code -
main.qml
Rectangle {
id: rootWindow
color: "black"
Visualizer {}
}
Visualizer.qml
import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Extras 2.12
import Qt3D.Input 2.12
import QtQuick.Scene3D 2.12
import QtQuick 2.12 as QQ2
Scene3D {
id: scene3d
anchors.fill: parent
focus: true
aspects: ["input", "logic"]
cameraAspectRatioMode: Scene3D.AutomaticAspectRatio
Entity {
id: sceneRoot
Camera {
id: camera
projectionType: CameraLens.PerspectiveProjection
fieldOfView: 45
nearPlane: 0.1
farPlane: 1000.0
position: Qt.vector3d(0.0, 0.0, 40.0)
upVector: Qt.vector3d(0.0, 1.0, 0.0)
viewCenter: Qt.vector3d(0.0, 0.0, 0.0)
}
FirstPersonCameraController {
camera: camera
}
components: [
RenderSettings {
activeFrameGraph: ForwardRenderer {
camera: camera
clearColor: "transparent"
Viewport {
id: viewport
normalizedRect: Qt.rect(0, 0, 1, 1)
}
}
},
InputSettings {
id: inputSettings
}
]
PhongMaterial {
id: material
}
Mesh {
id: sphereMesh
// source: "images/face3d/face_bse_mesh.obj"
source: "images/robo-obj-pose4/source/d2f0cff60afc40f5afe79156ec7db657.obj"
}
Transform {
id: modelTransform
property real userAngle: 0.0
matrix: {
var m = Qt.matrix4x4()
m.rotate(userAngle, Qt.vector3d(0, 1, 0))
// m.translate(Qt.vector3d(20, 0, 0))
return m
}
}
QQ2.NumberAnimation {
target: modelTransform
property: "userAngle"
duration: 10000
from: 0
to: 360
loops: QQ2.Animation.Infinite
running: true
}
Entity {
id: sphereEntity
components: [sphereMesh, material, modelTransform]
}
OrbitCameraController{
id: orbitCamera
camera: camera
}
}
}
So after a lot of asking around I have found the solution to this. It's a fairly simple enough trick.
You just need to add the following code in the Mesh, and that takes care of resizing the model in it's containing window.
Mesh {
----
onStatusChanged: {
if(status == Mesh.Ready)
camera.viewAll()
}
}
Sometimes while rendering the model its edges tend to go beyond the boundaries of the parent window. Adding some anchors.margins in the root Scene3D usually takes care of that.

How to change the texture or colour of an object in Qt3D with QML?

I have a project, where i have one 3d object (.obj file) and i want to click on this object. For the first test i would be happy to change the texture or colour of the object. As far as i know it's called picking. Do you guys know how to manage this in qt3d? My whole project is written in qml, so it would be great if i could do the picking with qml (without c++), but if it's necessary im ready to try it that way, too.
My project is structured as followed:
I have an Entity as rootEntity and 3D-Entity, where my mesh is loaded. This structure is in an own qml file called View3d.qml. Now I set a Scene3D in my main.qml and load setup an instance of View3d.
I am using the Qt 5.5 beta with included qt3d on a windows 8.1 64Bit system, if its necessary.
The easiest way is that you add Texture to your .obj file with blender and then add it to your project .for doing this by using blender there is a lot of tutorials, see this How to Add Texture and this video.
another way is to use Texture and Texture2D
look at this code as an Example :
I have 2 qml class
in main.qml :
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Scene3D 2.12
Window {
visible: true
width: 640
height: 480
title: qsTr("Hello World")
Scene3D
{
id : scene3d
anchors.fill: parent
focus: true
aspects: ["render", "logic", "input"]
hoverEnabled: true
cameraAspectRatioMode: Scene3D.AutomaticAspectRatio
antialiasing: true
RootEntity
{
id:root
}
}
}
and in RootEntity.qml :
import QtQuick 2.12
import Qt3D.Core 2.12
import Qt3D.Render 2.12
import Qt3D.Input 2.12
import Qt3D.Extras 2.12
Entity {
id: sceneRoot
readonly property var textureModel: [texture1, texture2, texture3, texture4]
readonly property Texture texture1: TextureLoader {
source: "qrc:/images/image.png"
}
readonly property Texture texture2: TextureLoader {
source: "qrc:/images/wood.jpg"
}
readonly property Texture texture3: Texture2D {
format: Texture.RGBA8_UNorm
textureImages: TextureImage {
source:"qrc:/images/image.png"
}
}
readonly property Texture texture4: Texture2D {
format: Texture.RGBA8_UNorm
textureImages: TextureImage {
source:"qrc:/images/wood.jpg"
}
}
Camera {
id: camera
projectionType: CameraLens.PerspectiveProjection
fieldOfView: 45
aspectRatio: 16/9
nearPlane : 0.1
farPlane : 1000.0
position: Qt.vector3d( 0.0, 20.0, -40.0 )
upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
viewCenter: Qt.vector3d( 0.0, 0.0, 0.0 )
}
OrbitCameraController {
camera: camera
}
components: [
RenderSettings {
activeFrameGraph: ForwardRenderer {
clearColor: "#333339"
camera: camera
}
},
// Event Source will be set by the Qt3DQuickWindow
InputSettings { }
]
CuboidMesh { id: mesh }
NodeInstantiator {
id: instantiator
model: sceneRoot.textureModel
Entity {
readonly property Transform transform: Transform {
readonly property real angle: model.index / instantiator.count * Math.PI * 2
translation: Qt.vector3d(Math.cos(angle) * 10, 0, Math.sin(angle) * 10)
scale: 10
}
readonly property DiffuseMapMaterial material: DiffuseMapMaterial {
diffuse: model.modelData
ambient: "white"
}
components: [ mesh, material, transform ]
}
}
}
the output is :
See the demos/qt3d/teaservice. This shows how to do picking (ie selection of an object using the mouse). Note you need the qt3d demo, not the QML teaservice.

Resources