qtdoc/examples/demos/xr_physicsbase_teleportation/PhysicsbaseTeleporter.qml

192 lines
7.4 KiB
QML

// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import QtQuick
import QtQuick3D
import QtQuick3D.Physics
Node {
id: teleporter
required property var rayPicker //any object that has implemented rayPick(pos, dir)
required property Node cameraOrigin
required property Node camera
required property Node beamHandle
required property CharacterController characterController
property real cameraSnapRotation: 30
property real xStickValue: 0
property real yStickValue: 0
property alias screenVisibility: screenValueFader.value
property bool targetValid: false
property color rayHitColor: "green"
property color rayMissColor: "red"
property int blinkSpeed: 150
property real movementChangedThreshold: 5
property real xzMovementTeleportationThreshold: 30
property real yMovementTeleportationThreshold: 10
property bool teleporting: false
property bool rotating: false
function teleportTo(position, byCharacterController = false) {
teleporter.teleporting = true
let offset = camera.scenePosition.minus(cameraOrigin.scenePosition)
let cameraOriginPosition = position.minus(offset)
cameraOriginPosition.y = position.y
let characterControllerHeight = teleporter.characterController.scale.y * 50 + //half of the controller height
50 * teleporter.characterController.scale.x //half of the cap of the controller capsule shape
if (byCharacterController)
cameraOriginPosition.y -= characterControllerHeight
screenValueFader.blink(()=>{
teleporter.doTeleportation(cameraOriginPosition,
//putting back the height of the character controller
Qt.vector3d(position.x, position.y + (byCharacterController ? 0 : characterControllerHeight), position.z))
},()=>{
characterControllerLastYPosition = characterController.scenePosition.y
teleporter.teleporting = false
}, teleporter.blinkSpeed)
}
function rotateBy(degrees) {
teleporter.rotating = true
let r = Quaternion.fromEulerAngles(0, degrees, 0)
let origin = Qt.vector3d(camera.position.x, 0, camera.position.z)
let mappedOrigin = cameraOrigin.rotation.times(origin).plus(cameraOrigin.position)
let rotatedOrigin = r.times(origin)
let mappedRO = cameraOrigin.rotation.times(rotatedOrigin).plus(cameraOrigin.position)
let delta = mappedRO.minus(mappedOrigin)
doRotation(cameraOrigin.rotation.times(r), cameraOrigin.position.minus(delta))
teleporter.rotating = false
}
signal doTeleportation(var cameraOriginPosition, var characterControllerPosition)
signal doRotation(var cameraOriginRotation, var cameraOriginPosition)
readonly property bool xPlusRotation: xStickValue > 0.5
onXPlusRotationChanged: {
if (xPlusRotation)
rotateBy(-cameraSnapRotation)
}
readonly property bool xMinusRotation: xStickValue < -0.5
onXMinusRotationChanged: {
if (xMinusRotation)
rotateBy(cameraSnapRotation)
}
readonly property vector3d cameraPosition: camera.scenePosition
onCameraPositionChanged: {
var deltaXZ = teleporter.camera.scenePosition.minus(teleporter.characterController.scenePosition)
deltaXZ.y = 0.0
let deltaLen = deltaXZ.length()
if (deltaLen >= teleporter.movementChangedThreshold &&
teleporter.rotating == false &&
teleporter.teleporting === false)
characterController.movement = deltaXZ.times(10.)
else
characterController.movement = Qt.vector3d(0, 0, 0)
if (deltaLen >= teleporter.xzMovementTeleportationThreshold &&
teleporter.rotating == false &&
teleporter.teleporting === false &&
(characterController.collisions & CharacterController.Down) &&
(characterController.collisions & CharacterController.Side) ) {
teleportTo(characterControllerPosition, true)
}
}
readonly property vector3d characterControllerPosition: teleporter.characterController.scenePosition
property real characterControllerLastYPosition: 0
onCharacterControllerPositionChanged: {
let deltaY = characterControllerPosition.y - characterControllerLastYPosition
if (Math.abs(deltaY) > teleporter.yMovementTeleportationThreshold &&
teleporter.rotating === false &&
teleporter.teleporting === false &&
characterController.collisions == CharacterController.Down)
teleportTo(characterControllerPosition, true)
}
ValueFader {
id: screenValueFader
}
TargetIndicator {
id: targetIndicator
}
BeamModel {
id: beamModel
color: teleporter.targetValid ? teleporter.rayHitColor : teleporter.rayMissColor
}
FrameAnimation {
running: teleporter.yStickValue > 0.7
onTriggered: {
teleporter.updateTarget()
}
onRunningChanged: {
if (running) {
beamModel.show()
}else {
beamModel.hide()
targetIndicator.hide()
if (teleporter.targetValid)
teleporter.teleportTo(targetIndicator.scenePosition)
}
}
}
function updateTarget() : bool {
// Not a pure gravity parabola: We want a flatter curve
let beamPositions = [];
let pos = beamHandle.scenePosition
const dx = beamHandle.forward.x
const dz = beamHandle.forward.z
const a = Qt.vector3d(dx * 2, -4, dz * 2)
let d = beamHandle.forward.times(50)
let index = 0
let hit = false
let pickResult = null
let penetrate = false
let lastPos = Qt.vector3d(pos.x, pos.y, pos.z)
let characterPos = characterController.scenePosition
let handleToCharacterDir = pos.minus(characterPos)
pickResult = teleporter.rayPicker.rayPick(characterPos, handleToCharacterDir.normalized())
if (pickResult.objectHit && pickResult.distance <= handleToCharacterDir.length() + 25) {
penetrate = true
beamModel.hide()
}else {
beamModel.show()
beamPositions.push(Qt.vector3d(pos.x, pos.y, pos.z))
for (let i = 0; !hit && i < 50; ++i) {
pos = pos.plus(d)
d = d.plus(a)
let lastPosToPos = pos.minus(lastPos)
pickResult = teleporter.rayPicker.rayPick(lastPos, lastPosToPos.normalized())
hit = pickResult.objectHit && pickResult.distance <= lastPosToPos.length() + 25
beamPositions.push(Qt.vector3d(pos.x, pos.y, pos.z))
lastPos = Qt.vector3d(pos.x, pos.y, pos.z)
}
beamModel.generate(beamPositions)
}
if (!penetrate && hit && pickResult.sceneNormal.normalized().y > 0.9) {
teleporter.targetValid = true
targetIndicator.moveTo(pickResult.scenePosition)
targetIndicator.show()
} else {
teleporter.targetValid = false
targetIndicator.hide()
}
return teleporter.targetValid
}
}