I want to render two 3d models with qml and control the rotation, the obj2 is the child of obj1, how to make obj2 follow obj1's rotation. I tried the qml like that(but i don't know how to assign the rotate angle of obj2), main.qml:
Item{
Scene3D {
id: scene3D
anchors.fill: parent
anchors.verticalCenter: parent.verticalCenter
focus: true
aspects: "input"
}
obj1 {
id: obj1
obj2 {
id: obj2
}
}
}
obj2.qml
Entity {
id: sceneRoot
Transform {
id: j2Transform
property real rotate_x : 0
property real rotate_y: 0
property real rotate_z : 0
Scale {
scale: 0.5
}
Rotate {
id: rotate_x
angle: // how to assign?
axis: Qt.vector3d(1, 0, 0)
}
Rotate {
id: rotate_y
angle: // how to assign?
axis: Qt.vector3d(0, 1, 0)
}
Rotate {
id: rotate_z
angle:// how to assign?
axis: Qt.vector3d(0, 0, 1)
}
Translate {
property real translation: 1
dy: 0
}
}
Mesh {
id: j2Mesh
source: "../res/obj/obj2.obj"
}
}
Thank you.
Related
Introduction
In my QtQuick (QML) project I need to use 3D model of dice (a cubic one) that should be rotated during animation process. I use View3D, because rotating dice will be inside of 3D-insertion which is inside my 2D project. I have downloaded a free-to-use dice model (.fbx) and successfully imported it in my project in form of ".mesh" file via Balsam Asset Import Tool. By choosing created mesh for a source of model and also applying the texture of dice via PrincipledMaterial, I perfectly see my dice model inside View3D.
The problem
When I tried to animate the rotation process of model, I noticed, that dice rotates incorrectly. I decided to look its axis via QtQuick3D.Helpers' AxisHelper and saw that starting points of axes X and Y located just fine but the Z axis' starting point is shifted from the center of the model by about 10 units.
What did I try
I tried to look up for useful properties of Model QML type (and Node because Model inherits Node type) that may allowed me to modify or specify the rotation axis of the object but I did not find anything. On the Stack Overflow there is one similar question but it does not helped me, because I should use View3D, not the whole Qt3D with Entity. Moreover, I tried to put translate property but it has only x and y fields and I did not seen any changes by modifying these.
What I want to know
I would like to know if there is a way to place rotation axis of 3D model inside the center of it. May be some Balsam Asset Import Tool's options can help me with that (I tried some options like dropNormals, removeComponentNormals, fbxPreservePivots but did not seen any changes)?
Also I am not good at 3D Modelling, so could you please tell me if .mesh or .fbx files of 3D models store the information of axis rotation or something like that.
One group of axes is a 3D scene axes, and other group is dice model axes
The code I have (View3DArea.qml - custom type)
import QtQuick
import QtQuick3D
import QtQuick3D.Helpers
Rectangle
{
id: root
width: parent.width/1.5
height: width
anchors.centerIn: parent
color: "lightgrey"
View3D
{
anchors.fill: parent
Node
{
PerspectiveCamera
{
id: _camera
z: 100
}
DirectionalLight
{
}
// Applying the texture
PrincipledMaterial
{
id: _priDiceMaterial
baseColorMap: Texture
{
source: "assets/assets/Dice_Diffuse.jpg.png"
}
}
// Model information
Model
{
id: _diceModel
source: "/assets/assets/dice_001.mesh"
materials: [_priDiceMaterial]
eulerRotation: Qt.vector3d(0, 0, 0)
position: Qt.vector3d(20, 20, 20)
AxisHelper {} // Visual axes of dice model
}
AxisHelper { id: _sceneAxis } // Visual axes of 3D scene
}
}
WasdController
{
controlledObject: _camera
}
}
[EDIT]
I reworked the answer by putting the Model inside the Node. You can change the axis of your Model by setting x, y or z properties. Then you apply rotation on the Node:
Node
{
id: node
Model
{
id: _diceModel
source: "/assets/assets/dice_001.mesh"
materials: [_priDiceMaterial]
x: 100
y: 100
z: 100
}
eulerRotation: Qt.vector3d(0, 45, 0)
}
Because dice is involved. I experimented with mixing #Cube and #Cylinder to create a dice effect. The approach uses the above strategy by offsetting the #Cylinder in different places. A pain point with this approach is, on certain faces, the #Cylinder needs to be rotated.
import QtQuick
import QtQuick.Controls
import QtQuick3D
Page {
background: Rectangle { color: "#848895" }
property color blk: Qt.rgba(0.2, 0.2, 0.2, 0.75)
property color wht: Qt.rgba(0.7, 0.7, 0.7, 0.75)
property color red: Qt.rgba(0.7, 0.2, 0.2, 0.75)
Node {
id: standAloneScene
DirectionalLight { ambientColor: Qt.rgba(1.0, 1.0, 1.0, 1.0) }
Node {
id: node
Model {
source: "#Cube"
materials: [
DefaultMaterial { diffuseColor: blk }
]
}
// 6
Dot { x: -25; y: 50; z: -25 }
Dot { x: 25; y: 50; z: -25 }
Dot { x: -25; y: 50 }
Dot { x: 25; y: 50 }
Dot { x: -25; y: 50; z: 25 }
Dot { x: 25; y: 50; z: 25 }
// 1
Dot { y: -50; c: red }
// 4
Dot { x: 50; y: -20; z: -25; r: "z"; c: red }
Dot { x: 50; y: 20; z: -25; r: "z"; c: red }
Dot { x: 50; y: -20; z: 25; r: "z"; c: red }
Dot { x: 50; y: 20; z: 25; r: "z"; c: red }
// 3
Dot { x: -50; y: -20; z: -25; r: "z" }
Dot { x: -50; r: "z" }
Dot { x: -50; y: 20; z: 25; r: "z" }
// 2
Dot { z: 50; x: 20; r: "x" }
Dot { z: 50; x: -20; r: "x" }
// 5
Dot { z: -50; x: -25; y: -25; r: "x" }
Dot { z: -50; x: 25; y: -25; r: "x" }
Dot { z: -50; r: "x" }
Dot { z: -50; x: -25; y: 25; r: "x" }
Dot { z: -50; x: 25; y: 25; r: "x" }
}
OrthographicCamera {
id: cameraOrthographicFront
lookAtNode: node
y: 800; z: 1000
}
}
View3D {
anchors.fill: parent
importScene: standAloneScene
camera: cameraOrthographicFront
}
NumberAnimation {
target: node
property: "eulerRotation.y"
loops: Animation.Infinite
running: true
from: 720; to: 0
duration: 10000
}
NumberAnimation {
target: node
property: "eulerRotation.x"
loops: Animation.Infinite
running: true
from: 360; to: 0
duration: 10000
}
}
// Dot.qml
import QtQuick
import QtQuick3D
Model {
property string r: ""
property color c: wht
source: "#Cylinder"
materials: [
DefaultMaterial { diffuseColor: c }
]
scale: Qt.vector3d(0.2, 0.05, 0.2)
eulerRotation.x: r === 'x' ? 90 : 0
eulerRotation.y: r === 'y' ? 90 : 0
eulerRotation.z: r === 'z' ? 90 : 0
}
You can Try it Online!
Description/ Code
I have a Qt Quick 3D View and corresponding scene that was designed to be compiled on Qt 6.3.0
import QtQuick
import QtQml
import QtQuick3D
import QtQuick3D.Helpers
Window {
width: 800
height: 600
visible: true
property var selectedItem
property bool mousePressed: false
function multiply_vectors(vec1, vec2) {
return Qt.vector3d(vec1.x * vec2.x, vec1.y * vec2.y, vec1.z * vec2.z);
}
View3D {
renderMode: View3D.Inline
camera: camera
anchors.fill: parent
width: 800
height: 600
x: 0
y: 0
id: view
environment: SceneEnvironment {
clearColor: "black"
backgroundMode: SceneEnvironment.Color
depthTestEnabled: false
depthPrePassEnabled: true
}
Model {
id: rootEntity
pickable: true
source: "#Cube"
materials: PrincipledMaterial {
baseColor: "red"
roughness: 0.1
}
position: Qt.vector3d(25.0, 15.0, -60.0)
scale: Qt.vector3d(1.0, 1.0, 1.0)
}
PerspectiveCamera {
id: camera
position.z: 330.0
position.y: 0.75
eulerRotation.x: -12
clipNear: 0.0
clipFar: 1600.0
}
MouseArea {
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
id: mouseArea
onPressed: function (mouse) {
var result = view.pick(mouse.x, mouse.y);
if (result.objectHit) {
selectedItem = result.objectHit;
mousePressed = true;
} else {
mousePressed = false;
}
}
onMouseXChanged: function(mouse) {
if (mousePressed) {
var viewCoords = view.mapFromGlobal(mouseArea.mapToGlobal(mouse.x, mouse.y));
var sceneCoords = Qt.vector3d(viewCoords.x, viewCoords.y, 0);
var worldCoords = view.mapTo3DScene(sceneCoords);
worldCoords.z = selectedItem.z
selectedItem.position = multiply_vectors(worldCoords, Qt.vector3d(Math.abs(camera.z - selectedItem.z), Math.abs(camera.z - selectedItem.z), 1.0))
}
}
onReleased: function (mouse) {
mousePressed = false
}
}
Component.onCompleted: {
camera.lookAt(rootEntity)
}
}
}
Overview
The use case is that whenever the mouse is pressed while pointing at the cube, whenever the mouse moves it will cause the cube to move along with it to the corresponding point in the 3d Scene.
This works great when looking from a point that is on the same z-axis. However when looking at the object from a point say along the x-axis, the model will move along the x-axis instead of following the mouse position.
Question
How can I modify the business logic in onMouseXChanged: function(mouse) { to correctly transform the matrix (or equivalent transform) to consistently match the mouse position irregardless of the camera's position relative to the Model?
If I understood you correctly, you need to move the object with the mouse parallel to the camera regardless of the camera position and model scaling? I admit that I don't have a solution, but still it's better than the original code. First of all, do not set the clipNear to 0, it would make the frustum degenerate and break the projection math.
Secondly, I would suppose that the code which sets the object position should look like
selectedItem.position = view.mapTo3DScene(
Qt.vector3d(mouse.x, mouse.y,
view.mapFrom3DScene(selectedItem.position).z))
The docs say that mapFrom3DScene/mapTo3DScene should interpret the z coordinate as the distance from the near clip plane of the frustum to the mapped position. However when I move it towards the sides of the window the object gets larger, whereas it should get smaller.
Here's the complete code with a few corrections of mine:
import QtQuick
import QtQml
import QtQuick3D
import QtQuick3D.Helpers
Window {
width: 800
height: 600
visible: true
property var selectedItem
property bool mousePressed: false
View3D {
renderMode: View3D.Inline
camera: camera
anchors.fill: parent
width: 800
height: 600
x: 0
y: 0
id: view
environment: SceneEnvironment {
clearColor: "black"
backgroundMode: SceneEnvironment.Color
depthTestEnabled: false
depthPrePassEnabled: true
}
Model {
id: rootEntity
pickable: true
source: "#Cube"
materials: PrincipledMaterial {
baseColor: "red"
roughness: 0.1
}
position: Qt.vector3d(25.0, 15.0, -60.0)
scale: Qt.vector3d(2.0, 1.0, 0.5)
}
PerspectiveCamera {
id: camera
position.z: 330.0
position.y: 100
position.x: 700
eulerRotation.x: -12
// Note 1: clipNear shouldn't be 0, otherwise
// it would break the math inside the projection matrix
clipNear: 1.0
clipFar: 1600.0
}
MouseArea {
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
id: mouseArea
onPressed: function (mouse) {
var result = view.pick(mouse.x, mouse.y);
if (result.objectHit) {
selectedItem = result.objectHit;
mousePressed = true;
} else {
mousePressed = false;
}
}
onPositionChanged: function(mouse) {
if (mousePressed) {
// Note 2: recalculate the position, since MouseArea has
// the same geometry as View3D we can use coords directly
selectedItem.position = view.mapTo3DScene(
Qt.vector3d(mouse.x, mouse.y,
view.mapFrom3DScene(selectedItem.position).z))
}
}
onReleased: function (mouse) {
mousePressed = false
}
}
Component.onCompleted: {
camera.lookAt(rootEntity)
}
}
}
After spending a while experimenting with different approaches, I found that mapping the mouse coordinates to the 3d space wasn't fully supported by the Qt API in terms of when the mouse is not fixed over an active object.
So, instead, the way that I made a workout was by casting a new RayCast each time the mouse moves and storing the offset when the mouse is pressed originally and then translating the item based on the result of the raycast and lining up the offset by translating by the normalized matrix with a small scalar.
onMouseXChanged: function (mouse) {
if (mousePressed) {
if (selectedItem != null) {
var result = view.pick(mouse.x, mouse.y)
if (result.objectHit) {
if (result.objectHit == selectedItem) {
var mouseGlobalPos = mouseArea.mapToGlobal(
mouse.x, mouse.y)
var mouseViewPos = view.mapFromGlobal(
mouseGlobalPos)
var mouseScenePos = result.scenePosition
var resultPos = result.position
/* here we subtract the result of the new raycast by the starting offset and then normalize
* the result and multiply it by a scalar 3 to determine the amount of offset the Model
* under the mouse is from where the mouse was originally pressed, so we can translate it */
var differencePos = resultPos.minus(
startMousePressSelectedItemLocalDragOffset).normalized(
).times(3)
selectedItem.position = selectedItem.position.plus(
differencePos)
I am trying to rotate a rectangle from-to a specified angle, but I'm not sure I understand the docs. My code below runs, and my started and completed slots print the correct angles. But the rectangle has not rotated onscreen. What am I missing?
Rectangle {
width: 100
height: 100
RotationAnimation {
id: rotateCritter
duration: 1000
property real lastAngle: 0
onStarted: {
lastAngle = to;
console.log("Rotating from "+from+" to "+to)
}
onStopped: {
console.log("Done rotating from "+from+" to "+to)
from = lastAngle;
}
}
}
// On click totate the critter to the new angle
rotateCritter.to = 45
rotateCritter.start()
Your RotationAnimation is missing a target. Though it is the child of the Rectangle, this relationship does not automatically make the Rectangle the target of the animation; it must be explicit. I have given the Rectangle an id and color, and made this the target of the animation:
Rectangle {
id: critter
width: 100
height: 100
color: "red"
RotationAnimation {
id: rotateCritter
target: critter
duration: 1000
property real lastAngle: 0
onStarted: {
lastAngle = to;
console.log("Rotating from "+from+" to "+to)
}
onStopped: {
console.log("Done rotating from "+from+" to "+to)
from = lastAngle;
}
}
}
Another idea besides using a RotationAnimation object is to just animate on the Rectangle's own rotation property using a Behavior.
Rectangle {
id: rect
width: 100
height: 100
Behavior on rotation {
NumberAnimation {
duration: 1000
}
}
}
rect.rotation = 45 // Animates to 45 degrees
...
rect.rotation = 0 // Animates back to 0 degrees
I'm using Qt version 5.6.0 from windows 8.1. I draw a shape that has 4 sides and the ability to drag its apexes.I want to move apexes of shape with arrow keys.I use this code but it does not work.
Point.qml:
Item {
id: root
signal dragged()
property alias color :point.color
Rectangle {
id:point
anchors.centerIn: parent
width: 20
height: 20
opacity: 0.2
MouseArea {
anchors.fill: parent
drag.target: root
onPositionChanged: {
if(drag.active) {
dragged()
}
}
onClicked: point.focus=true;
}
// Keys.onPressed: {
// console.log("move");
// if (event.key === Qt.Key_Left) {
// console.log("move left");
// event.accepted = true;
// point.x-=1;
// }
// }
// Keys.onRightPressed: {
// console.log("move");
// point.x+=1;
// }
// Keys.onLeftPressed: {
// console.log("move");
// point.x-=1;
// }
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Left: point.x-=1;
break;
case Qt.Key_Right: point.x+=1;
break;
case Qt.Key_Up: point.y-=1;
break;
case Qt.Key_Down: point.y+=1;
break;
}
}
focus: true;
}}
main.qml:
Point {
id: pointA
x: 50
y: 50
}
Point {
id: pointB
x: 250
y: 50
}
Point {
id: pointC
x: 250
y: 250
}
Point {
id: pointD
x: 50
y: 250
}
Item {
anchors.fill: parent
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
var ctx = canvas.getContext('2d');
ctx.moveTo(pointA.x, pointA.y);
ctx.lineTo(pointB.x, pointB.y);
ctx.lineTo(pointC.x, pointC.y);
ctx.lineTo(pointD.x, pointD.y);
ctx.lineTo(pointA.x, pointA.y);
ctx.stroke();
}
Component.onCompleted: {
pointA.dragged.connect(repaint)
pointB.dragged.connect(repaint)
pointC.dragged.connect(repaint)
pointD.dragged.connect(repaint)
}
function repaint() {
var ctx = getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.beginPath();
requestPaint()
}
}
}
Update:
main.qml:
PhotoPreview {
id : photoPreview
anchors.fill : parent
//focus:true //visible
visible: capture
}
photopreview.qml:
Item {
id:mywin
property real defaultSize:mywin.width
property var currentFrame: undefined
property real surfaceViewportRatio: 1.5
focus: true
ScrollView {
anchors.fill: parent
flickableItem.interactive: true
frameVisible: true
highlightOnFocus: true
Flickable {
id: flick
anchors.fill: parent
contentWidth: parent.width
contentHeight: parent.height
property alias source :image.source
signal closed
Rectangle {
id: photoFrame
width: parent.width
height: parent.height
color:"transparent"
scale:defaultSize / parent.width
Behavior on scale { NumberAnimation { duration: 200 } }
Behavior on x { NumberAnimation { duration: 200 } }
Behavior on y { NumberAnimation { duration: 200 } }
smooth: true
antialiasing: true
Image {
id:image
anchors.fill: parent
fillMode: Image.PreserveAspectFit
smooth: true
}
PinchArea {
anchors.fill: parent
pinch.target: photoFrame
pinch.minimumRotation: -360
pinch.maximumRotation: 360
pinch.minimumScale: 0.1
pinch.maximumScale: 10
pinch.dragAxis: Pinch.XAndYAxis
property real zRestore: 0
onSmartZoom: {
if (pinch.scale > 0) {
photoFrame.rotation = 0;
photoFrame.scale = Math.min(mywin.width, mywin.height) / Math.max(image.sourceSize.width, image.sourceSize.height) * 0.85
photoFrame.x = flick.contentX + (flick.width - photoFrame.width) / 2
photoFrame.y = flick.contentY + (flick.height - photoFrame.height) / 2
zRestore = photoFrame.z
photoFrame.z = ++mywin.highestZ;
} else {
photoFrame.rotation = pinch.previousAngle
photoFrame.scale = pinch.previousScale
photoFrame.x = pinch.previousCenter.x - photoFrame.width / 2
photoFrame.y = pinch.previousCenter.y - photoFrame.height / 2
photoFrame.z = zRestore
--mywin.highestZ
}
}
MouseArea {
id: dragArea
hoverEnabled: true
anchors.fill: parent
drag.target: photoFrame
scrollGestureEnabled: false // 2-finger-flick gesture should pass through to the Flickable
onPressed: {
photoFrame.z = ++mywin.highestZ;
}
onWheel: {
if (wheel.modifiers & Qt.ControlModifier) {
photoFrame.rotation += wheel.angleDelta.y / 120 * 5;
if (Math.abs(photoFrame.rotation) < 4)
photoFrame.rotation = 0;
} else {
photoFrame.rotation += wheel.angleDelta.x / 120;
if (Math.abs(photoFrame.rotation) < 0.6)
photoFrame.rotation = 0;
var scaleBefore = photoFrame.scale;
photoFrame.scale += photoFrame.scale * wheel.angleDelta.y / 120 / 10;
}
}
}
}
Point {
id: pointA
x: image.width/4
y: image.height/4
color: "blue"
}
Point {
id: pointB
x: image.width/2
y: image.height/2
color: "blue"
}
Point {
id: pointD
x: image.width/4
y: image.height/2
color: "red"
}
Point {
id: pointC
x: image.width/2
y: image.height/4
color: "red"
}
Item {
anchors.fill: parent
Canvas {
id: canvas
anchors.fill: parent
onPaint: {
//...
}
Component.onCompleted: {
//...
}
function repaint() {
//..
}
}
}
}
}
}}
You are using ScrollView that inherit FocusScope, which needs to have focus:true to forward it.
You instead are setting focus to PhotoPreview, which is plain Item that are not supposed to be focused.
So you need to simple remove focus:visible and set it to ScrollView.
The better fix is to remove that redundant Item, and leave ScrollView as PhotoPreview root item, with all its properties and signals.
In Point.qml the key event handler tries to change the x, y of point, however point is a rectangle that anchors.centerIn: parent. The key event handler should change root x, y instead:
//in Point.qml
case Qt.Key_Left:
root.x-=1;
break;
Point now changes it's position when keyboard event is triggered. Next, Point needs to notify the Canvas in main.qml to repaint. Point can emit dragged signal after changing it's x, y:
//in Point.qml
Keys.onPressed: {
switch(event.key) {
case Qt.Key_Left:
root.x-=1;
dragged();
break;
case Qt.Key_Right:
root.x+=1;
dragged();
break;
case Qt.Key_Up:
root.y-=1;
dragged();
break;
case Qt.Key_Down:
root.y+=1;
dragged();
break;
}
}
I want to create some sort of Vocabulary Trainer.
I have a Card QML File what shoud represent some kind of a record card where you can see the Vocabulary. When you've answered, the card should turn around 180° and a new Word/Text should be visible on it.
So far I've created a Rectangle for the Card and a Transformation for the Rotation split up in two PropertyAnimations.
For the sake of simplicity I just want the animation to happen when I'm clicking on the Card. Then the Card turns from 0 to 90 degrees. Afterwards the text should be changed. And at last the Card should turn from -90 to 0 degrees. So I'm looking for a logic that allows me to execute an animation, changes a property (text) instantly and executing another animation as a sequence.
Here is my Code so far:
import QtQuick 2.2
import QtGraphicalEffects 1.0
Item {
Rectangle {
id: card
anchors.fill: parent
border.width: 1
border.color: "grey"
antialiasing: true
Text {
id: question
text: "test test test"
anchors.centerIn: card
}
transform: Rotation {
id: rotation
origin.x: (card.width / 2)
origin.y: (card.height / 2)
axis {
x: 0
y: 1
z: 0
}
angle: 0
}
MouseArea {
anchors.fill: card
onClicked: {
// Code for Turning Card arround
rotate_away.start()
question.text = "abcabcabc"
rotate_new.start()
}
}
PropertyAnimation {
id: rotate_away
target: rotation
properties: "angle"
from: 0
to: 90
duration: 1000
}
PropertyAnimation {
id: rotate_new
target: rotation
properties: "angle"
from: -90
to: 0
duration: 1000
}
}
}
So the problem is this part:
rotate_away.start()
question.text = "abcabcabc"
rotate_new.start()
The text changes but only the 2'nd animation will be executed.
I tried
while (rotate_away.running) {}
to wait for the 1st animation but then the application gets stuck.
I think the animations should be played sequently by using SequentialAnimation. Please revisit your code as follows:
MouseArea {
anchors.fill: card
onClicked: {
// Code for Turning Card around
// rotate_away.start()
// question.text = "abcabcabc"
// rotate_new.start()
fullRotate.start();
}
}
SequentialAnimation {
id: fullRotate
PropertyAnimation {
id: rotate_away
target: rotation
properties: "angle"
from: 0
to: 90
duration: 1000
}
PropertyAction {
target: question
property: "text"
value: "abcabcabc"
}
PropertyAnimation {
id: rotate_new
target: rotation
properties: "angle"
from: -90
to: 0
duration: 1000
}
}
Also, I recommend Flipable which is meant for flipping effects.