Revision control
++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
++
++ import QtQuick
++ import QtQuick3D
++
++ Model {
++ id: root
++ source: isSphere() ? "#Sphere" : "#Rectangle"
++ property real size: 1
++ property real contrast: 0.5
++
++ property Node cameraNode: null
++ property real scaleDistance: 100 // Cursor is scaled up when further away than this distance
++
++ property real sf: size / 100 // private property
++
++ opacity: 0.5
++
++ scale: Qt.vector3d(sf, sf, sf)
++
++ enum CursorStyle {
++ Hidden,
++ Flat,
++ Sphere
++ }
++ property int cursorStyle: XrCursor.CursorStyle.Flat
++
++ function isSphere() : bool {
++ return cursorStyle === XrCursor.CursorStyle.Sphere
++ }
++
++ CustomMaterial {
++ id: targetMaterial
++ shadingMode: CustomMaterial.Unshaded
++ cullMode: Material.BackFaceCulling
++ vertexShader: "shaders/cursor.vert"
++ fragmentShader: "shaders/cursor.frag"
++
++ property real opacity: root.cursorStyle === XrCursor.CursorStyle.Hidden ? 0.0 : root.opacity
++ property real contrast: root.contrast
++ }
++
++ PrincipledMaterial {
++ id: sphereMaterial
++ baseColor: "white"
++ }
++
++ materials: isSphere() ? sphereMaterial : targetMaterial
++
++ function setPositionAndOrientation(scenePos : vector3d, sceneNormal : vector3d) {
++ if (isSphere()) {
++ position = scenePos
++ } else {
++ const newPos = scenePos.minus(sceneNormal.normalized().times(-0.1))
++ rotation = Quaternion.lookAt(newPos, scenePos,
++ Qt.vector3d(0, 0, -1), Qt.vector3d(0, 1, 0))
++ position = newPos
++ }
++ if (cameraNode && scaleDistance) {
++ const distance = scenePos.minus(cameraNode.position).length()
++ const distanceScale = Math.max(1, distance / root.scaleDistance)
++ sf = size * distanceScale / 100
++ }
++ }
++ }