How can I shift the Rotation Axis of imported model? (QML, QtQuick3D, View3D) - qt

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!

Related

Qt QML MapItem Rotation issue

I have a QML OSM map and a MapQuickItem with Text source item:
MapQuickItem {
property alias rulerRotationAngle: rulerRotation.angle
id: rulerTextMapItem
visible: false
width: 2
height: 2
transform: Rotation {
id: rulerRotation
origin.x: rulerText.width/2;
origin.y: rulerText.height/2;
angle: 0
}
anchorPoint.x: rulerText.width/2
anchorPoint.y: rulerText.height/2
z:5
sourceItem: Text {
id: rulerText; horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
color: Material.color(Material.Amber, Material.Shade100)
text: "0.0 km";
}
}
I also have two points (QtPositioning.coordinate) and I want the text to rotate depending on the angle of the straight line (MapPolyLine) drawn between those points:
function drawRuler()
{
rulerLine.path = [];
rulerLine.addCoordinate(r_firstpoint);
rulerLine.addCoordinate(r_secondpoint);
rulerTextMapItem.visible = true;
rulerTextMapItem.coordinate = QtPositioning.coordinate((r_firstpoint.latitude+r_secondpoint.latitude)/2, (r_firstpoint.longitude+r_secondpoint.longitude)/2);
var atan = Math.atan2(r_secondpoint.longitude-r_firstpoint.longitude, r_secondpoint.latitude-r_firstpoint.latitude);
var angle = ((atan*180)/Math.PI); //used by another MapItem
var textAngle = angle+270;
if(textAngle>90 & textAngle<270) { textAngle+=180 }
if(angle>90 & angle<270) { angle +=180 }
rulerTextMapItem.rulerRotationAngle = textAngle;
}
However, text rotates correctly only at angles that are multiples of 90 degrees. At an angle of 45 degrees, the text deviates from the mappolyline by about 10-20 degrees.
I have no clue why it happens and appreciate any help.
Tried to move transform.origin of MapQuickItem - angle difference only gets bigger.
Tried to use Math.Atan instead of Math.Atan2 - no difference.
The main issue is this line and the order of inputs:
var atan = Math.atan2(
r_secondpoint.longitude-r_firstpoint.longitude,
r_secondpoint.latitude-r_firstpoint.latitude);
latitude should come before longitude, i.e.
var atan = Math.atan2(
r_secondpoint.latitude-r_firstpoint.latitude,
r_secondpoint.longitude-r_firstpoint.longitude);
Generally speaking, to use Math.atan2() to convert to an angle, you need to use one of the following patterns:
let radians = Math.atan2(vectorY, vectorX)
let degrees = Math.atan2(vectorY, vectorX) * 180 / Math.PI
Also over large angles, you definitely should use project your angular coordinates to a flat projection, e.g. QtPositioning.coordToMercator. (This point was raised in one of the earlier comments).
For very small angles you can get away with it because the earth can be approximated to a flat earth directly from angular coordinates, but, as the area goes, this fact quickly disappears.
The following code demonstrates Math.atan2() and how it must work with (vectorY, vectorX) inputs. It has two draggable squares and you watch that the text will always follow the direction of the blue line no matter where the squares are:
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Shapes
Page {
id: page
width: 200; height: 200
property int startX: rect1.x + rect1.width / 2
property int startY: rect1.y + rect1.height / 2
property int finishX: rect2.x + rect2.width / 2
property int finishY: rect2.y + rect2.height / 2
Rectangle {
id: rect1
x: 40; y: 40
width: 40; height: 40
color: "red"
Drag.active: dragArea.drag.active
Drag.hotSpot.x: 20
Drag.hotSpot.y: 20
MouseArea {
id: dragArea
anchors.fill: parent
drag.target: parent
}
}
Rectangle {
id: rect2
x: 400; y: 250
width: 40; height: 40
color: "red"
Drag.active: dragArea2.drag.active
Drag.hotSpot.x: 20
Drag.hotSpot.y: 20
MouseArea {
id: dragArea2
anchors.fill: parent
drag.target: parent
}
}
Shape {
id: shape
ShapePath {
strokeWidth: 4
strokeColor: "blue"
startX: page.startX
startY: page.startY
PathLine {
x: page.finishX
y: page.finishY
}
}
}
Item {
x: (startX + finishX) / 2
y: (startY + finishY) / 2
rotation: Math.atan2(finishY - startY, finishX - startX) * 180 / Math.PI
Frame {
anchors.centerIn: parent
background: Rectangle {
border.color: "black"
}
Text {
text: "Hello World"
}
}
}
}
You can Try it Online!

Qt Quick bindings not updated at startup

Trying to make a very simple node editor in Qt Quick.
The most tricky part seems to be the Connection item, which has to track the two absolute positions of the source and destination IOPorts.
The following code works fine, except that the bindings are not evaluated (or are evaluated in the wrong order) at startup, resulting in the connection line to be in the wrong position at startup:
which is then adjusted to the correct position as soon as either node is moved:
I thought about adding:
Component.onCompleted: {
start = mapFromItem(from, 0, 0)
end = mapFromItem(to, 0, 0)
}
to Connection, which fixes the startup problem, but breaks the bindings.
So I resorted to this ugly hack:
Component.onCompleted: {
from.x = from.x+1
from.x = from.x-1
}
Is there a better solution to this problem?
(files available in GitHub repository, link at bottom)
main.qml:
import QtQuick 2.15
import QtQuick.Window 2.15
Window {
width: 420
height: 280
visible: true
title: qsTr("Node editor")
Node {
x: 20
y: 40
width: 120
height: 50
IOPort {id: o1; x: 0; y: parent.height}
}
Node {
x: 260
y: 140
width: 120
height: 50
IOPort {id: i1; x: 0; y: 0}
}
Connection {
from: o1; to: i1
}
}
Node.qml:
import QtQuick 2.0
Rectangle {
id: rect
border.color: "black"
border.width: 2
color: "#eee"
MouseArea {
anchors.fill: parent
drag.target: rect
}
}
IOPort.qml:
import QtQuick 2.0
Item {
id: root
property real size: 15
property real globalX: x + parent.x
property real globalY: y + parent.y
property real globalZ: 1 + parent.z
Rectangle {
id: rect
x: -width/2
y: -height/2
width: root.size
height: root.size
border.color: "black"
color: "gray"
}
}
Connection.qml:
import QtQuick 2.0
import QtQuick.Shapes 1.15
Shape {
id: root
property IOPort from
property IOPort to
x: Math.min(from.globalX, to.globalX)
y: Math.min(from.globalY, to.globalY)
z: Math.max(from.globalZ, to.globalZ) + 0.5
width: Math.abs(from.globalX - to.globalX)
height: Math.abs(from.globalY - to.globalY)
property point start: from.globalX, from.globalY, to.globalX, to.globalY, mapFromItem(from, 0, 0)
property point end: from.globalX, from.globalY, to.globalX, to.globalY, mapFromItem(to, 0, 0)
ShapePath {
strokeWidth: 4
strokeColor: "black"
startX: root.start.x
startY: root.start.y
PathLine {
x: root.end.x
y: root.end.y
}
}
}
GitHub repository: https://github.com/fferri/qtquick-simple-node-editor.git
I cannot currently reproduce the error but I think I found a problem:
Your Connection root's start and end properties are missing a "forced" dependency on that same object's x and y
For some reason you decided to not only to set the coordinates for path's points but also move the whole Connection element itself. So if root's x and y happen to update after start and end did, the latter still contain relative coordinates of the point when x and y were still default (probably 0,0) and thus the line becomes displaced by x,y pixels.
Try changing them this way (I also removed dependencies you shouldn't need):
property point start: from.globalX, from.globalY, x, y, mapFromItem(from, 0, 0)
property point end: x, y, to.globalX, to.globalY, mapFromItem(to, 0, 0)
(There were more suggestions here in case this didn't work but they were removed because the above seems to have solved the issue)

QtQuick pathCurve curve line on mouse click event

Hey posting this question to learn how to curve a line onclick i.e using path-curve i want to curve a line but not hard-coded it occur on mouse click event like if clicked on (400,320) then line at that position Y position will change (let say +75) as in image it is hard-coded but i want curve onclick wherever mouse click event occur it should curve
Here is my implementation. It's not exactly the shape you want. It seems that you can not get something like your image with PathCurve but surely you have much more control over the outcome with PathCubic.
Canvas {
id: canvas
width: 640; height: 640
contextType: "2d"
Path {
id: myPath
startX: 320; startY: 0
PathCurve { id: curvePoint_top; x: 320; y: curvePoint.y - 80 }
PathCurve { id: curvePoint; x: 320; y: 320 }
PathCurve { id: curvePoint_bottom; x: 320; y: curvePoint.y + 80 }
PathCurve { x: 320; y: 640 }
}
onPaint: {
context.clearRect(0,0,width,height);
context.strokeStyle = Qt.rgba(.4,.6,.8);
context.path = myPath;
context.stroke();
}
MouseArea{
anchors.fill: parent
onClicked: {
curvePoint.x = mouseX;
curvePoint.y = mouseY;
canvas.requestPaint();
}
}
}

How to add a rectangle to a QML ChartView?

I want to put a number of rectangles as overlays to show regions of interest on a ChartView of ScatterSeries. However when I try to do this it is clearly using a different coordinate system to that of the ScatterSeries as it is drawn in a completely different place.
For example the following is intended to draw a rectangle that captures all of the ScatterSeries but it just draws a small green rectangle top left as shown in the screenshot.
ChartView {
id: view
Layout.fillWidth : true
Layout.fillHeight : true
Rectangle {
id: rec
x: 30
y: 50
width: 40
height: 10
color: "green"
}
ScatterSeries{
id: series
XYPoint { x: 30; y: 50 }
XYPoint { x: 50; y: 60 }
XYPoint { x: 60; y: 50 }
XYPoint { x: 70; y: 60 }
axisX: ValueAxis {
min: 0
max: 100
}
axisY: ValueAxis {
min: 0
max: 100
}
}
The documentation suggests that the rectangle should use the coordinate system of the parent ChartView. I assume I actually want it to use the coordinate system of the ChartView scene. How do I do this?
To translate from the ScatterSeries coordinate system to pixel coordinates to place a child in ChartView use mapToPosition(...):
function updateRectangle() {
var topLeftPoint = view.mapToPosition(Qt.point(30,60), series)
var bottomRightPoint = view.mapToPosition(Qt.point(70,50), series)
rec.x = topLeftPoint.x
rec.y = topLeftPoint.y
rec.width = bottomRightPoint.x - topLeftPoint.x
rec.height = bottomRightPoint.y - topLeftPoint.y
}
Where series is your ScatterSeries and rec is your Rectangle.
Invoke the update function whenever the chart points get recalculated (e.g. after creation and size changes).
See also related question How to map QChart coordinate to QChartView point?

How to make the child follow parent's rotation in qml

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.

Resources