I have a simple 3D scene and a box that I want to rotate around its origin point:
Entity {
components: [ mesh, phongMaterial, transform ]
CuboidMesh {
id: mesh
yzMeshResolution: Qt.size(2, 2)
xzMeshResolution: Qt.size(2, 2)
xyMeshResolution: Qt.size(2, 2)
zExtent: 1
xExtent: 1
yExtent: 2
}
PhongAlphaMaterial {
id: phongMaterial
property color randColor: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
ambient: randColor
diffuse: randColor
specular: randColor
shininess: 1.0
alpha: 0.4
}
Transform {
id: transform
property real userAngle: 0.0
scale: 1
rotation: fromAxisAndAngle(Qt.vector3d(0, 0, 1), userAngle)
translation: Qt.vector3d( 0.0, 0.0, 0.0 )
}
QQ2.NumberAnimation {
target: transform
property: "userAngle"
duration: 2000
loops: QQ2.Animation.Infinite
running: true
easing.type: QQ2.Easing.InOutQuad
from: 0
to: 360
}
}
By default the origin point is in the center of the box. I need to move the origin point down as shown on the image:
But I have no clue how to do that. I've tried to play with Transform.translation but that just move the shape along the axis. I've tried to play with Transform.rotateAround(point, real angle, vector3d axis) but I see no changes. I've changed the point value but the origin point remains in the center of the shape.
Transform {
id: transform
property real userAngle: 0.0
matrix: rotateAround(Qt.point(1,1), userAngle, Qt.vector3d( 0.0, 0.0, 1.0 ))
}
Ok, I've found a solution. The first parameter of the rotateAround is 3d vector, not point. So using
matrix: rotateAround(Qt.vector3d(0.0, 1.0, 0.0), userAngle, Qt.vector3d( 0.0, 0.0, 1.0 ))
does the trick.
Related
I need scale and rotate a Rectangle around mouse point. When the Rectangle is not rotated my solution works fine, but if I apply Rotation transform I face the problem - my Rectangle move to an unexpected point. In my solution I use a MouseArea for drag the Rectangle, Scale and Rotation transforms for scale and rotate.
import QtQuick 2.0
Rectangle {
id: root
color: "gray"
Rectangle {
color: "black"
width: 360
height: 200
opacity: 0.5
x: 500
y: 350
}
Rectangle {
id: sample
color: "green"
width: 360
height: 200
opacity: 0.5
x: 500
y: 350
property real currX: 0
property real currY: 0
property real currZoom: 1
property real maxZoom: 5
property real minZoom: 0.5
transform: [
Scale {
id: scaler
origin.x: sample.currX
origin.y: sample.currY
xScale: sample.currZoom
yScale: sample.currZoom
},
Rotation{
id: rotation
origin.x: 180
origin.y: 100
angle: 30
}
]
MouseArea{
id: mouseArea
anchors.fill: parent
drag.target: sample
onClicked: mouse => {
zoom(true, mouse.x, mouse.y)
}
onWheel: (wheel) => {
var isIn = wheel.angleDelta.y > 0
zoom(isIn, wheel.x, wheel.y)
}
function zoom(isIn, x, y) {
var prevZoom = sample.currZoom
var prevX = sample.currX
var prevY = sample.currY
sample.currX = x
sample.currY = y
sample.currZoom = calculateZoom(isIn, prevZoom)
sample.x = sample.x + (prevX - sample.currX) * (1 - prevZoom)
sample.y = sample.y + (prevY - sample.currY) * (1 - prevZoom)
printSamplePostion()
}
function calculateZoom(isIn, prevZoom) {
var newZoom = isIn ? prevZoom + 0.1 : prevZoom - 0.1
if (newZoom > mouseArea.maxZoom)
newZoom = mouseArea.maxZoom
if (newZoom < mouseArea.minZoom)
newZoom = mouseArea.minZoom
return newZoom
}
function printSamplePostion() {
console.log("== x: 500 y: 350 ======")
console.log("-- x: " + sample.x)
console.log("-- y: " + sample.y)
console.log("=======================")
}
}
}
}
Thanks in advance
For reasons that are more complex than this minimal testcase, I need to have a entity (childEntity, the magenta box) child of another entity (parentEntity, the cyan box), but childEntity should be independent of parentEntity's transform.
Therefore I add this handler:
QtQuick.Connections {
target: parentTransform
onMatrixChanged: {
// cancel parent's transform
var m = parentTransform.matrix
var i = m.inverted()
childTransform.matrix = i
// debug:
console.log(parentTransform.matrix.times(i))
}
}
which works well for cancelling out parent's translation and rotation, but not for scale.
When parent's scale3D is not [1,1,1] and rotation is also set, then childEntity appears distorted, despite the product of parentTransform.matrix times childTransform.matrix gives the 4x4 identity. Why?
Minimal testcase: (load into a QQuickView)
import QtQml 2.12 as QtQml
import QtQuick 2.12 as QtQuick
import QtQuick.Controls 2.12 as QtQuickControls
import QtQuick.Scene3D 2.0
import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
import Qt3D.Extras 2.0
Scene3D {
function change_translation_and_rotation() {
parentTransform.translation.x = 0.1
parentTransform.translation.y = 0.5
parentTransform.translation.z = 2
parentTransform.rotationX = 30
parentTransform.rotationY = 60
parentTransform.rotationZ = 10
}
function change_rotation_and_scale() {
parentTransform.rotationX = 30
parentTransform.rotationY = 60
parentTransform.rotationZ = 10
parentTransform.scale3D.x = 0.1
parentTransform.scale3D.y = 0.5
parentTransform.scale3D.z = 2
}
function reset_transform() {
parentTransform.translation.x = -0.5
parentTransform.translation.y = 0
parentTransform.translation.z = 0.5
parentTransform.rotationX = 0
parentTransform.rotationY = 0
parentTransform.rotationZ = 0
parentTransform.scale3D.x = 1
parentTransform.scale3D.y = 1
parentTransform.scale3D.z = 1
}
data: [
QtQml.Connections {
target: parentTransform
onMatrixChanged: {
// cancel parent's transform
var m = parentTransform.matrix
var i = m.inverted()
childTransform.matrix = i
// debug:
console.log(parentTransform.matrix.times(i))
}
},
QtQuick.Column {
spacing: 5
QtQuick.Repeater {
id: buttons
model: ["change_translation_and_rotation", "change_rotation_and_scale", "reset_transform"]
delegate: QtQuickControls.Button {
text: modelData.replace(/_/g, ' ')
font.bold: focus
onClicked: {focus = true; scene3d[modelData]()}
}
}
}
]
id: scene3d
anchors.fill: parent
aspects: ["render", "logic", "input"]
Entity {
id: root
components: [RenderSettings {activeFrameGraph: ForwardRenderer {camera: mainCamera}}, InputSettings {}]
Camera {
id: mainCamera
projectionType: CameraLens.PerspectiveProjection
fieldOfView: 45
aspectRatio: 16/9
nearPlane : 0.1
farPlane : 1000.0
position: Qt.vector3d(-3.46902, 4.49373, -3.78577)
upVector: Qt.vector3d(0.41477, 0.789346, 0.452641)
viewCenter: Qt.vector3d(0.0, 0.5, 0.0)
}
OrbitCameraController {
camera: mainCamera
}
Entity {
id: parentEntity
components: [
CuboidMesh {
xExtent: 1
yExtent: 1
zExtent: 1
},
PhongMaterial {
ambient: "#6cc"
},
Transform {
id: parentTransform
translation: Qt.vector3d(-0.5, 0, 0.5)
}
]
Entity {
id: childEntity
components: [
CuboidMesh {
xExtent: 0.5
yExtent: 0.5
zExtent: 0.5
},
PhongMaterial {
ambient: "#c6c"
},
Transform {
id: childTransform
translation: Qt.vector3d(-0.5, 0, 0.5)
}
]
}
}
QtQuick.Component.onCompleted: reset_transform()
}
}
The problem is that the QTransform node does not store the transformation as a general 4x4 matrix. Rather is decomposes the matrix into a 3 transformations that are applied in fixed order:
S - a diagonal scaling matrix
R - the rotation matrix
T - translation
and then applies it in the order T * R * S * X to a point X.
The documentation for the matrix property writes about this decomposition step https://doc.qt.io/qt-5/qt3dcore-qtransform.html#matrix-prop
So when the transformation on the parent is M = T * R * S, then the inverse on the child will be M^-1 = S^-1 * R^-1 * T^-1. Setting the inverse on the child QTransform will attempt to decompose it in the same way:
M^-1 = T_i * R_i * S_i = S^-1 * R^-1 * T^-1
That doesn't work, because particularly S and R don't commute like this.
You can test this in your code by comparing the value of childTransform.matrix and i after you set the childTransform.matrix.
I think the only solution is to rather have 3 QTransforms on entities nested above the child to implement the correct order of the inverses as S^-1 * R^-1 * T^-1.
It is pretty simple to compute the inverse S^-1, R^-1 T^-1 from the corresponding parameters in the parent QTransform.
I am trying to proceduraly generate Cube with a GeometryRenderer. Why when GeometryRenderer.primitiveType is set to LineLoop, Lines or TriangleFan everything works, but with Triangles or TrianglesStrip draws absolutely nothing?
PhongMaterial { id: material; }
GeometryRenderer {
id: renderer
primitiveType: GeometryRenderer.LineLoop
instanceCount: 1
geometry: Geometry {
attributes: [
Attribute {
name: defaultPositionAttributeName
attributeType: Attribute.VertexAttribute
vertexBaseType: Attribute.Float
vertexSize: 3
byteOffset: 0
byteStride: 3 * 4
count: 6
buffer: Buffer {
type: Buffer.VertexBuffer
data: new Float32Array([
// #1 triangle
-0.9, -0.5, 0.0,
-0.0, -0.5, 0.0,
-0.45, 0.5, 0.0,
// #2 triangle
0.0, -0.5, 0.0,
0.9, -0.5, 0.0,
0.45, 0.5, 0.0,
])
}
}
]
}
}
Entity {
components: [
renderer,
material
]
}
If I adapt your QML the following way then I can see two triangles:
import QtQuick 2.2
import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Extras 2.0
Entity {
id: root
PhongMaterial { id: material; diffuse: Qt.rgba(0.8, 0., 0., 1.0) }
GeometryRenderer {
id: renderer
primitiveType: GeometryRenderer.Triangles
instanceCount: 1
geometry: Geometry {
Attribute {
name: defaultPositionAttributeName
attributeType: Attribute.VertexAttribute
vertexBaseType: Attribute.Float
vertexSize: 3
byteOffset: 0
byteStride: 3 * 4
count: 6
buffer: Buffer {
type: Buffer.VertexBuffer
data: new Float32Array([
// #1 triangle
-0.9, -0.5, 0.0,
-0.0, -0.5, 0.0,
-0.45, 0.5, 0.0,
// #2 triangle
0.0, -0.5, 0.0,
0.9, -0.5, 0.0,
0.45, 0.5, 0.0,
])
}
}
}
}
components: [ renderer, material ]
}
I suppose the error is related to the wrong hierarchy of GeometryRenderer and Entity.
To get correct lighting you need to give the GeometryRenderer the vertexNormals too.
Edit:
As it is a common question whether a GeometryRenderer always needs an indexBuffer for primitiveType: Triangles, TriangleStrip, and TriangleFan, I'll come up with a somewhat more detailed answer.
A GeometryRenderer of primitiveType: Triangles does not necessarily need an index array (I checked the source code of Qt3D as I was unsure).
The reason you are not seeing your triangles is: You are defining the vertices in the wrong order! Change the order of the vertices in the vertexBuffer so that every three consecutive vertices form a triangle when going counterclockwise around the triangle. The triangle normal will then point at you.
Or, have a look onto your Entity from the opposite direction: you'll see the two triangles.
When using huge buffers and you don't want to repeat large amounts of vertices for memory/efficiency reasons you'll definitely should consider using an indexBuffer.
Is there any way to draw half dashed circle in QML? I drawn half circle in this way
var Circle = getContext("2d");
Circle.save();
var CircleGradient =
Circle.createLinearGradient(parent.width/4,parent.height,parent.width/4,0);
CircleGradient.addColorStop(0, firstGradientPoint);
CircleGradient.addColorStop(1, secondGradientPoint);
Circle.clearRect(0, 0, parent.width, parent.height);
Circle.beginPath();
Circle.lineCap = "round";
Circle.lineWidth = 10;
Circle.strokeStyle = CircleGradient
Circle.arc(parent.width/2, parent.height/2, canvas.radius - (Circle.lineWidth / 2), Math.PI/2, canvas.Value);
Circle.stroke();
Circle.restore();
Result
But how can I make it dashed like this.
I need
I know that this question is very outdated, but it might help someone. You can use Qt Quick Shapes (since Qt 5.10) to render what you want. It's not copy-paste code, but more of an approach:
Shape {
ShapePath {
id: shapePath
strokeColor: "black"
strokeStyle: ShapePath.DashLine
dashPattern: [6, 8]
fillColor: "transparent"
PathArc {
x: 0
y: radiusX + radiusY
radiusX: 100
radiusY: 100
useLargeArc: true
}
}
}
PathArc documentation has pretty much everything you need. Here are some more Shape Examples.
I know QML little bit but never coded.
But you can solve your problem by logic.
Here is the logic- Code below is pseudo, will not work but will give you an idea.
Draw the small arcs in loop with spaces in between.
//DECLARE YOUR ANGLES START AND END
startAngle = 0.0;
endAngle = pi/20;// 10 ARCS AND 10 SPACES
while (q++ < 10){
Circle.arc(parent.width/2, parent.height/2, canvas.radius - (Circle.lineWidth / 2), startAngle, endAngle, canvas.Value)
//LEAVE SPACE AND CREATE NEW START AND END ANGLE.
startAngle = endAngle + endAngle;
endAngle = startAngle + endAngle;
}
In a QML application I have an item that is moving around the screen (not rotating). I want to display an indicator that rotates around this item, pointing away from the center of the screen, a fixed distance away from the center of the item.
The following simplified QML application performs this goal, by making the indicator a child of the item, and translating it to the desired location. However, when I try to rotate the indicator (the commented-out code) I cannot find any values for origin.x and .y that work. It feels like the QML scene graph calculates X/Y positioning in a way unlike any I've experienced.
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id: win
visible:true; width:600; height:300
property real padding: 50
property real angle: 0
property real _rads: angle * Math.PI/180
Timer {
interval:50; running:true; repeat:true
onTriggered:win.angle = (new Date/50) % 360
}
Rectangle {
id:object; color:'blue'
width:50; height:width
property real xOffset: Math.cos(_rads)
property real yOffset: Math.sin(_rads)
x: win.width/2 + xOffset * (win.width/2 - padding*2)
y: win.height/2 + yOffset * (win.height/2 - padding*2)
Rectangle {
id:indicator; color:'red'
property real centerOffset: 40
width:10; height:width*2
x: object.width/2 + object.xOffset * centerOffset - width/2
y: object.height/2 + object.yOffset * centerOffset - height/2
// transform: Rotation { origin.x:0; origin.y:0; angle:win.angle }
}
}
}
I've tried making the indicator not be a child of the item. I've tried using Translate in the transform stack instead of X/Y positions. All of them result in amusing-but-incorrect rotations.
How can I simply rotate the indicator around its own center, or otherwise achieve my goal?
You might think of it as a clock and build yourself a clockhand.
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id: win
visible:true; width:600; height:300
property real padding: 50
property real angle: 0
property real _rads: angle * Math.PI/180
Timer {
interval:50; running:true; repeat:true
onTriggered:win.angle = (new Date/50) % 360
}
Rectangle {
id:object; color:'blue'
width:50; height:width
property real xOffset: Math.cos(_rads)
property real yOffset: Math.sin(_rads)
x: win.width/2 + xOffset * (win.width/2 - padding*2)
y: win.height/2 + yOffset * (win.height/2 - padding*2)
Text {
width: 250
height: 250
x: -100
y: -100
text: '▲'
color: 'red'
font.pixelSize: 20
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignTop
transform: Rotation {
angle: win.angle + 90
origin.x: 125
origin.y: 125
}
}
Text {
x: 15
y: -125
width: 20
height: 20
text: '▲'
color: 'red'
font.pixelSize: 20
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
transform: Rotation {
angle: win.angle + 90
origin.x: 10
origin.y: 150
}
}
Rectangle {
id: clockhand
width: 1
height: 100
color: 'black'
anchors {
centerIn: parent
}
rotation: win.angle + 90
Text {
text: '▲'
color: 'red'
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.top
bottomMargin: -5
}
font.pixelSize: 20
}
}
}
}
Just turn the Clockhand into an Item and remove the color, to make it invisible.
Similar to #derM I have a solution that makes use of Item.rotation. However, with regard to the rest, I have generalized it by avoiding cos and sin since they are not required.
To demonstrate I create 3 SVG images crosshairs.svg, marker.svg, and triangle.svg. I placed crosshairs.svg in the center of the screen. I animate marker.svg by making it bounce around the screen "pong" style. Now, the secret sauce is the placement and orientation of triangle.svg.
To place an offset a triangle relative to the marker. I put the triangle Image inside an Item component. The Item component has no area, it merely has x, y, and rotation set. The Image component is placed relative to the Item and we need to compute its relative placement.
Because the triangle.svg is 16x16, I placed it at (20, -8) relative to the marker. If I had chosen (-8, -8) the SVG would sit on top of the marker. Because I put it at (20, -8) it puts it beyond the marker. Lastly, I compute the rotation using Math.atan2() on the vector between the marker and the crosshairs:
Item {
x: marker.x
y: marker.y
rotation: Math.atan2(
marker.y - crosshairs.y,
marker.x - crosshairs.x
) * 180 / Math.PI
Image {
source: "triangle.svg"
x: 20
y: -8
//cache: false
}
}
Here's a full working demo:
import QtQuick 2.15
import QtQuick.Controls 2.15
Page {
Timer {
interval: 50
running: true
repeat: true
onTriggered: marker.animate()
}
Rectangle {
id: frame
anchors.centerIn: parent
width: parent.width / 2
height: parent.height / 2
color: "#ffe"
border.color: "grey"
clip: true
Image {
id: crosshairs
anchors.centerIn: parent
source: "crosshair.svg"
//cache: false
}
Item {
id: marker
x: parent.width/2
y: parent.height/2
property int dx: 2
property int dy: 2
property int size: 20
Image {
anchors.centerIn: parent
source: "marker.svg"
//cache: false
}
function animate() {
x += dx;
y += dy;
if (x + size / 2 >= parent.width || x - size / 2 <= 0) {
dx = -dx;
x += dx;
x += dx;
}
if (y + size / 2 >= parent.height || y - size / 2 <= 0) {
dy = -dy;
py += dy;
py += dy;
}
}
}
Item {
x: marker.x
y: marker.y
rotation: Math.atan2(
marker.y - crosshairs.y,
marker.x - crosshairs.x
) * 180 / Math.PI
Image {
source: "triangle.svg"
x: 20
y: -8
//cache: false
}
}
}
}
//crosshair.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path stroke="grey" d="M6 0L6 12M 0 6L 12 6"/></svg>
//marker.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path stroke="grey" fill="#ffe" d="M0 0 L20 0 L20 20 L0 20 z"/></svg>
//triangle.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path stroke="grey" fill="red" d="M16 8 L0 0 L 0 16z"/></svg>
You can Try it Online!