1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
|
// Copyright (C) 2025 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick3D
import QtQuick3D.Xr
pragma ComponentBehavior: Bound
XrController {
id: theController
poseSpace: XrController.AimPose
readonly property vector3d pickDirection: Qt.vector3d(0, 0, -1)
required property QtObject view // an object that has implemented rayPick(pos, dir)
property XrCursor xrCursor: null
property int pickStatus: PickResult.Null
property bool enableVirtualMouse: false
property bool enableThumbstickMove: true
property real yeetDistance: 400
property real emptyRayLength: 400
//! [signals]
property Model hoveredObject: null
signal objectPressed(obj: Model, pos: vector3d, direction: vector3d)
signal moved(pos: vector3d, direction: vector3d)
signal released()
signal objectGrabbed(obj: Model)
//! [signals]
QtObject {
id: priv
property QtObject hitObject: null
property bool isGrabbing: false
property bool isInteracting: false
property vector3d offset
property quaternion rotation
property Node grabbedObject
function tryGrab() {
theController.objectGrabbed(hitObject as Model)
}
function doGrab(obj: Node) {
grabbedObject = obj
if (grabbedObject) {
const scenePos = grabbedObject.scenePosition
const sceneRot = grabbedObject.sceneRotation
offset = theController.mapPositionFromScene(scenePos)
rotation = theController.rotation.inverted().times(sceneRot)
isGrabbing = true
if (xrCursor)
xrCursor.visible = false
}
}
function ungrab() {
hitObject = grabbedObject
grabbedObject = null
isGrabbing = false
}
function handlePress() {
theController.objectPressed(hitObject, theController.scenePosition, theController.forward)
isInteracting = true
pickRay.pressed = true
}
function handleRelease() {
isInteracting = false
theController.released()
pickRay.pressed = false
}
function moveObject() {
if (grabbedObject) {
let newPos = theController.scenePosition.plus(theController.rotation.times(offset))
let newRot = theController.sceneRotation.times(rotation)
if (grabbedObject.parent) {
newPos = grabbedObject.parent.mapPositionFromScene(newPos)
newRot = grabbedObject.parent.sceneRotation.inverted().times(newRot)
}
grabbedObject.setPosition(newPos)
grabbedObject.setRotation(newRot)
}
}
function yeet(delta: real) {
const localForward = Qt.vector3d(0, 0, -1)
const rayPos = localForward.times(pickRay.length)
const yeetOffset = offset.minus(rayPos)
pickRay.length = Math.max(10, Math.min(pickRay.length * (1 + delta/10), theController.yeetDistance))
offset = yeetOffset.plus(localForward.times(pickRay.length))
}
function findObject(pickResult : var) {
const didHit = pickResult.hitType !== PickResult.Null
theController.pickStatus = pickResult.hitType
if (didHit) {
pickRay.hit = true
pickRay.length = pickResult.distance * 0.75
hitObject = pickResult.objectHit
} else {
pickRay.hit = false
pickRay.length = theController.emptyRayLength
hitObject = null
}
theController.hoveredObject = hitObject
}
function handleMove() {
const dir = theController.mapDirectionToScene(pickDirection)
const pickResult = theController.view.rayPick(scenePosition, dir)
if (xrCursor) {
const didHit = pickResult.hitType !== PickResult.Null
xrCursor.visible = didHit && !priv.isGrabbing
if (didHit)
xrCursor.setPositionAndOrientation(pickResult.scenePosition, pickResult.sceneNormal)
}
if (isInteracting)
theController.moved(theController.scenePosition, theController.forward)
else if (isGrabbing)
moveObject()
else
findObject(pickResult)
}
}
function startGrab(obj: Node) {
priv.doGrab(obj)
}
PickRay {
id: pickRay
visible: !priv.isGrabbing
length: theController.emptyRayLength
}
onRotationChanged: {
priv.handleMove()
}
XrInputAction {
id: grabAction
controller: theController.controller
actionId: [XrInputAction.SqueezeValue, XrInputAction.SqueezePressed]
onPressedChanged: {
if (pressed) {
priv.tryGrab()
} else {
priv.ungrab()
}
}
}
XrInputAction {
id: triggerAction
controller: theController.controller
actionId: [XrInputAction.TriggerValue, XrInputAction.TriggerPressed, XrInputAction.IndexFingerPinch]
onPressedChanged: {
if (pressed)
priv.handlePress()
else
priv.handleRelease()
}
}
XrInputAction {
id: yeetAction
enabled: theController.enableThumbstickMove
controller: theController.controller
actionId: XrInputAction.ThumbstickY
}
FrameAnimation {
id: yeetAnimation
running: priv.isGrabbing && Math.abs(yeetAction.value) > 0.1
onTriggered: priv.yeet(yeetAction.value * frameTime * 30)
}
XrVirtualMouse {
enabled: theController.enableVirtualMouse
source: theController
view: theController.view as XrView
leftMouseButton: triggerAction.pressed
}
}
|