breezy-desktop/kwin/src/qml/CameraController.qml

183 lines
6.1 KiB
QML

import QtQuick
import QtQuick3D
Item {
id: cameraController
required property Camera camera
required property var fovDetails
Displays {
id: displays
}
property real aspectRatio: effect.displayResolution[0] / effect.displayResolution[1]
property real lensDistanceRatio: effect.lensDistanceRatio
property bool sbsEnabled: effect.sbsEnabled
property bool customBannerEnabled: effect.customBannerEnabled
property bool smoothFollowEnabled: effect.smoothFollowEnabled
property real lookAheadScanlineMs: effect.lookAheadConfig[2]
property var fovLengths: displays.diagonalToCrossFOVs(
displays.degreeToRadian(effect.diagonalFOV),
aspectRatio
);
property real fovHalfVerticalTangent: fovLengths.heightUnitDistance / 2.0;
property real fovHalfHorizontalTangent: fovLengths.widthUnitDistance / 2.0;
// if true, then smoothFollowEnabled just cleared and the orientation data is slerping back,
// continue to use the origin data for the duration of the Timer
property bool smoothFollowDisabling: false
property real clipNear: 10.0
property real clipFar: 10000.0
function ratesOfChange(orientations) {
const e0 = orientations[0].toEulerAngles();
const e1 = orientations[1].toEulerAngles();
const dt = effect.poseTimeElapsedMs;
const yawDegrees = (e0.y - e1.y) / dt;
const pitchDegrees = (e0.x - e1.x) / dt;
const rollDegrees = (e0.z - e1.z) / dt;
return {
eulerEnd: e0,
eulerStart: e1,
yawDegrees: yawDegrees,
yaw: displays.degreeToRadian(yawDegrees),
pitchDegrees: pitchDegrees,
pitch: displays.degreeToRadian(pitchDegrees),
rollDegrees: rollDegrees,
roll: displays.degreeToRadian(rollDegrees)
};
}
function updateCamera(orientations, position, rates) {
camera.eulerRotation = applyLookAhead(
rates,
lookAheadMS(
effect.poseTimestamp,
effect.lookAheadConfig,
effect.lookAheadOverride
)
);
let lensVector = Qt.vector3d(0, 0, -fovDetails.lensDistancePixels);
// if we only have 3DoF, account for a bit of positional change based on orientation,
// don't do this for 6DoF to prevent doubling the positional movement due to rotation
if (!effect.poseHasPosition) lensVector = orientations[0].times(lensVector);
camera.position = position.times(fovDetails.fullScreenDistancePixels).plus(lensVector);
}
// how far to look ahead is how old the pose data is plus a constant that is either the default for this device or an override
function lookAheadMS(poseDateMs, lookAheadConfig, override) {
// how stale the pose data is
const dataAge = Date.now() - poseDateMs;
const lookAheadConstant = lookAheadConfig[0];
const lookAheadMultiplier = lookAheadConfig[1];
return (override === -1 ? lookAheadConstant : override) + dataAge;
}
function applyLookAhead(rates, lookAheadMs) {
return Qt.vector3d(
rates.eulerEnd.x + rates.pitchDegrees * lookAheadMs,
rates.eulerEnd.y + rates.yawDegrees * lookAheadMs,
rates.eulerEnd.z + rates.rollDegrees * lookAheadMs,
);
}
function updateProjection() {
camera.projection = buildPerspectiveMatrix();
}
function buildPerspectiveMatrix() {
const f = 1.0 / fovHalfVerticalTangent;
const nf = 1.0 / (clipNear - clipFar);
const m00 = f / aspectRatio;
const m11 = f;
const m22 = (clipFar + clipNear) * nf;
const m23 = (2.0 * clipFar * clipNear) * nf;
// Standard OpenGL-style projection matrix
return Qt.matrix4x4(
m00, 0, 0, 0,
0, m11, 0, 0,
0, 0, m22, m23,
0, 0, -1, 0
);
}
function applyRollingShutterShear(rates) {
// Convert to maximum shift at bottom of frame
const maxDxNdc = (rates.yaw * lookAheadScanlineMs) / fovHalfHorizontalTangent;
const maxDyNdc = -(rates.pitch * lookAheadScanlineMs) / fovHalfVerticalTangent;
let shx = maxDxNdc / 2.0;
let shy = maxDyNdc / 2.0;
const f = 1.0 / fovHalfVerticalTangent;
const nf = 1.0 / (clipNear - clipFar);
const m00 = f / aspectRatio;
const m11 = f;
const m22 = (clipFar + clipNear) * nf;
const m23 = (2.0 * clipFar * clipNear) * nf;
const r0c0 = m00;
const r0c1 = -(shx * m11) / 2.0;
const r0c2 = -(shx) / 2.0;
const r0c3 = 0.0;
const r1c0 = 0.0;
const r1c1 = m11 * (1.0 - shy / 2.0);
const r1c2 = -(shy) / 2.0;
const r1c3 = 0.0;
const r2c0 = 0.0;
const r2c1 = 0.0;
const r2c2 = m22;
const r2c3 = m23;
const r3c0 = 0.0;
const r3c1 = 0.0;
const r3c2 = -1.0;
const r3c3 = 0.0;
camera.projection = Qt.matrix4x4(
r0c0, r0c1, r0c2, r0c3,
r1c0, r1c1, r1c2, r1c3,
r2c0, r2c1, r2c2, r2c3,
r3c0, r3c1, r3c2, r3c3
);
}
Component.onCompleted: updateProjection();
FrameAnimation {
running: true
onTriggered: {
const orientations = (effect.smoothFollowEnabled || smoothFollowDisabling) ? effect.smoothFollowOrigin : effect.poseOrientations;
if (orientations && orientations.length > 0) {
const rates = ratesOfChange(orientations);
updateCamera(orientations, effect.posePosition, rates);
applyRollingShutterShear(rates);
}
}
}
Timer {
id: smoothFollowDisablingTimer
interval: 750
repeat: false
onTriggered: {
cameraController.smoothFollowDisabling = false;
}
}
onSmoothFollowEnabledChanged: {
smoothFollowDisablingTimer.stop();
smoothFollowDisabling = !smoothFollowEnabled;
if (smoothFollowDisabling) smoothFollowDisablingTimer.start();
}
}