| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- // Copyright (C) 2025 The Qt Company Ltd.
- // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
- import QtQuick
- import QtQuick3D
- Item {
- id: root
- required property Node targetNode
- enum Axis {
- PositiveZ = 0,
- NegativeZ = 1,
- PositiveY = 2,
- NegativeY = 3,
- PositiveX = 4,
- NegativeX = 5
- }
- // These are the 24 different rotations a rotation aligned on axes can have.
- // They are ordered in groups of 4 where the +Z,-Z,+Y,-Y,+X,-X axis is pointing
- // towards the screen (+Z). Inside this group the rotations are ordered to
- // rotate counter-clockwise.
- readonly property list<quaternion> rotations: [
- // +Z
- Qt.quaternion(1, 0, 0, 0),
- Qt.quaternion(Math.SQRT1_2, 0, 0, -Math.SQRT1_2),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(Math.SQRT1_2, 0, 0, Math.SQRT1_2),
- // -Z
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(0, -Math.SQRT1_2, -Math.SQRT1_2, 0),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, Math.SQRT1_2, -Math.SQRT1_2, 0),
- // +Y
- Qt.quaternion(0.5, 0.5, 0.5, 0.5),
- Qt.quaternion(Math.SQRT1_2, Math.SQRT1_2, 0, 0),
- Qt.quaternion(-0.5, -0.5, 0.5, 0.5),
- Qt.quaternion(0, 0, -Math.SQRT1_2, -Math.SQRT1_2),
- // -Y
- Qt.quaternion(0.5, -0.5, 0.5, -0.5),
- Qt.quaternion(0, 0, Math.SQRT1_2, -Math.SQRT1_2),
- Qt.quaternion(-0.5, 0.5, 0.5, -0.5),
- Qt.quaternion(-Math.SQRT1_2, Math.SQRT1_2, 0, 0),
- // +X
- Qt.quaternion(-0.5, -0.5, 0.5, -0.5),
- Qt.quaternion(-Math.SQRT1_2, 0, Math.SQRT1_2, 0),
- Qt.quaternion(-0.5, 0.5, 0.5, 0.5),
- Qt.quaternion(0, Math.SQRT1_2, 0, Math.SQRT1_2),
- // -X
- Qt.quaternion(0, Math.SQRT1_2, 0, -Math.SQRT1_2),
- Qt.quaternion(0.5, -0.5, 0.5, 0.5),
- Qt.quaternion(Math.SQRT1_2, 0, Math.SQRT1_2, 0),
- Qt.quaternion(0.5, 0.5, 0.5, -0.5),
- ]
- readonly property list<quaternion> xRotationGoals : [
- Qt.quaternion(0, 1, 0, 0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, 1, 0, 0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, 1, 0, 0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(-0, -1, -0, -0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(-0, -1, -0, -0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(-0, 1, -0, -0),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, 0, 0, -1),
- ]
- readonly property list<quaternion> yRotationGoals : [
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, 1, 0, 0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, 1, 0, 0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, 1, 0, 0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, 1, 0, 0),
- ]
- readonly property list<quaternion> zRotationGoals : [
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, 1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, 0, 0, -1),
- Qt.quaternion(0, 1, 0, 0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, 1, 0, 0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, 0, -1, 0),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, 1, 0, 0),
- Qt.quaternion(0, -1, 0, 0),
- Qt.quaternion(0, 0, 1, 0),
- Qt.quaternion(0, 1, 0, 0),
- Qt.quaternion(0, 0, -1, 0),
- ]
- // This function works by using a rotation to rotate x,y,z normal vectors
- // and see what axis-aligned rotation gives the closest distance to the
- // rotated normal vectors.
- function findClosestRotation(rotation, startI, stopI) {
- let rotationConjugated = rotation.conjugated();
- let xRotated = rotation.times(Qt.quaternion(0, 1, 0, 0)).times(rotationConjugated);
- let yRotated = rotation.times(Qt.quaternion(0, 0, 1, 0)).times(rotationConjugated);
- let zRotated = rotation.times(Qt.quaternion(0, 0, 0, 1)).times(rotationConjugated);
- var closestIndex = 0;
- var closestDistance = 123456789; // big number
- for (var i = startI; i < stopI ; i++) {
- let distance = xRotated.minus(xRotationGoals[i]).length() +
- yRotated.minus(yRotationGoals[i]).length() +
- zRotated.minus(zRotationGoals[i]).length();
- if (distance <= closestDistance) {
- closestDistance = distance;
- closestIndex = i;
- }
- }
- return closestIndex;
- }
- function quaternionAlign(rotation) {
- let closestIndex = findClosestRotation(rotation, 0, 24);
- return rotations[closestIndex];
- }
- function quaternionForAxis(axis, rotation) {
- let closestIndex = findClosestRotation(rotation, axis*4, (axis + 1)*4);
- return rotations[closestIndex];
- }
- function quaternionRotateLeft(rotation) {
- let closestIndex = findClosestRotation(rotation, 0, 24);
- let offset = (4 + closestIndex - 1) % 4;
- let group = Math.floor(closestIndex / 4);
- return rotations[offset + group * 4];
- }
- function quaternionRotateRight(rotation) {
- let closestIndex = findClosestRotation(rotation, 0, 24);
- let offset = (closestIndex + 1) % 4;
- let group = Math.floor(closestIndex / 4);
- return rotations[offset + group * 4];
- }
- signal axisClicked(int axis)
- signal ballMoved(vector2d velocity)
- QtObject {
- id: stylePalette
- property color white: "#fdf6e3"
- property color black: "#002b36"
- property color red: "#dc322f"
- property color green: "#859900"
- property color blue: "#268bd2"
- property color background: "#99002b36"
- }
- component LineRectangle : Rectangle {
- property vector2d startPoint: Qt.vector2d(0, 0)
- property vector2d endPoint: Qt.vector2d(0, 0)
- property real lineWidth: 5
- transformOrigin: Item.Left
- height: lineWidth
- readonly property vector2d offset: startPoint.plus(endPoint).times(0.5);
- width: startPoint.minus(endPoint).length()
- rotation: Math.atan2(endPoint.y - startPoint.y, endPoint.x - startPoint.x) * 180 / Math.PI
- }
- Rectangle {
- id: ballBackground
- anchors.centerIn: parent
- width: parent.width > parent.height ? parent.height : parent.width
- height: width
- radius: width / 2
- color: ballBackgroundHoverHandler.hovered ? stylePalette.background : "transparent"
- readonly property real subBallWidth: width / 5
- readonly property real subBallHalfWidth: subBallWidth * 0.5
- readonly property real subBallOffset: radius - subBallWidth / 2
- Item {
- anchors.centerIn: parent
- component SubBall : Rectangle {
- id: subBallRoot
- required property Node targetNode
- required property real offset
- property alias labelText: label.text
- property alias labelColor: label.color
- property alias labelVisible: label.visible
- property alias hovered: subBallHoverHandler.hovered
- property var initialPosition: Qt.vector3d(0, 0, 0)
- readonly property vector3d position: quaternionVectorMultiply(targetNode.rotation, initialPosition)
- signal tapped()
- function quaternionVectorMultiply(q, v) {
- var qv = Qt.vector3d(q.x, q.y, q.z)
- var uv = qv.crossProduct(v)
- var uuv = qv.crossProduct(uv)
- uv = uv.times(2.0 * q.scalar)
- uuv = uuv.times(2.0)
- return v.plus(uv).plus(uuv)
- }
- height: width
- radius: width / 2
- x: offset * position.x - width / 2
- y: offset * -position.y - height / 2
- z: position.z
- HoverHandler {
- id: subBallHoverHandler
- }
- TapHandler {
- acceptedButtons: Qt.LeftButton
- onTapped: (eventPoint, button)=>{
- subBallRoot.tapped()
- //eventPoint.accepted = true
- }
- }
- Text {
- id: label
- anchors.centerIn: parent
- }
- }
- SubBall {
- id: positiveX
- targetNode: root.targetNode
- width: ballBackground.subBallWidth
- offset: ballBackground.subBallOffset
- labelText: "X"
- labelColor: hovered ? stylePalette.white : stylePalette.black
- color: stylePalette.red
- initialPosition: Qt.vector3d(1, 0, 0)
- onTapped: {
- root.axisClicked(OriginGizmo.Axis.PositiveX)
- }
- }
- LineRectangle {
- endPoint: Qt.vector2d(positiveX.x + ballBackground.subBallHalfWidth, positiveX.y + ballBackground.subBallHalfWidth)
- color: stylePalette.red
- z: positiveX.z - 0.001
- }
- SubBall {
- id: positiveY
- targetNode: root.targetNode
- width: ballBackground.subBallWidth
- offset: ballBackground.subBallOffset
- labelText: "Y"
- labelColor: hovered ? stylePalette.white : stylePalette.black
- color: stylePalette.green
- initialPosition: Qt.vector3d(0, 1, 0)
- onTapped: {
- root.axisClicked(OriginGizmo.Axis.PositiveY)
- }
- }
- LineRectangle {
- endPoint: Qt.vector2d(positiveY.x + ballBackground.subBallHalfWidth, positiveY.y + ballBackground.subBallHalfWidth)
- color: stylePalette.green
- z: positiveY.z - 0.001
- }
- SubBall {
- id: positiveZ
- targetNode: root.targetNode
- width: ballBackground.subBallWidth
- offset: ballBackground.subBallOffset
- labelText: "Z"
- labelColor: hovered ? stylePalette.white : stylePalette.black
- color: stylePalette.blue
- initialPosition: Qt.vector3d(0, 0, 1)
- onTapped: {
- root.axisClicked(OriginGizmo.Axis.PositiveZ)
- }
- }
- LineRectangle {
- endPoint: Qt.vector2d(positiveZ.x + ballBackground.subBallHalfWidth, positiveZ.y + ballBackground.subBallHalfWidth)
- color: stylePalette.blue
- z: positiveZ.z - 0.001
- }
- SubBall {
- targetNode: root.targetNode
- width: ballBackground.subBallWidth
- offset: ballBackground.subBallOffset
- labelText: "-X"
- labelColor: stylePalette.white
- labelVisible: hovered
- color: Qt.rgba(stylePalette.red.r, stylePalette.red.g, stylePalette.red.b, z + 1 * 0.5)
- border.color: stylePalette.red
- border.width: 2
- initialPosition: Qt.vector3d(-1, 0, 0)
- onTapped: {
- root.axisClicked(OriginGizmo.Axis.NegativeX)
- }
- }
- SubBall {
- targetNode: root.targetNode
- width: ballBackground.subBallWidth
- offset: ballBackground.subBallOffset
- labelText: "-Y"
- labelColor: stylePalette.white
- labelVisible: hovered
- color: Qt.rgba(stylePalette.green.r, stylePalette.green.g, stylePalette.green.b, z + 1 * 0.5)
- border.color: stylePalette.green
- border.width: 2
- initialPosition: Qt.vector3d(0, -1, 0)
- onTapped: {
- root.axisClicked(OriginGizmo.Axis.NegativeY)
- }
- }
- SubBall {
- targetNode: root.targetNode
- width: ballBackground.subBallWidth
- offset: ballBackground.subBallOffset
- labelText: "-Z"
- labelColor: stylePalette.white
- labelVisible: hovered
- color: Qt.rgba(stylePalette.blue.r, stylePalette.blue.g, stylePalette.blue.b, z + 1 * 0.5)
- border.color: stylePalette.blue
- border.width: 2
- initialPosition: Qt.vector3d(0, 0, -1)
- onTapped: {
- root.axisClicked(OriginGizmo.Axis.NegativeZ)
- }
- }
- }
- HoverHandler {
- id: ballBackgroundHoverHandler
- acceptedDevices: PointerDevice.Mouse
- cursorShape: Qt.PointingHandCursor
- }
- DragHandler {
- id: dragHandler
- target: null
- enabled: ballBackground.visible
- onCentroidChanged: {
- if (centroid.velocity.x > 0 && centroid.velocity.y > 0) {
- root.ballMoved(centroid.velocity)
- }
- }
- }
- }
- }
|