Add zoom on focus logic
This commit is contained in:
parent
7fb48fe77d
commit
d7120fbc2e
|
|
@ -5,11 +5,17 @@
|
|||
http://www.kde.org/standards/kcfg/1.0/kcfg.xsd" >
|
||||
<kcfgfile name="kwinrc"/>
|
||||
<group name="Effect-breezy_desktop_effect">
|
||||
<entry name="DisplayDistance" type="Double">
|
||||
<entry name="FocusedDisplayDistance" type="Double">
|
||||
<default>0.85</default>
|
||||
<min>0.2</min>
|
||||
<max>2.5</max>
|
||||
<label>Focused Display Distance</label>
|
||||
</entry>
|
||||
<entry name="AllDisplaysDistance" type="Double">
|
||||
<default>1.05</default>
|
||||
<min>0.2</min>
|
||||
<max>2.5</max>
|
||||
<label>Display Distance</label>
|
||||
<label>All Displays Distance</label>
|
||||
</entry>
|
||||
</group>
|
||||
</kcfg>
|
||||
|
|
|
|||
|
|
@ -123,7 +123,8 @@ BreezyDesktopEffect::BreezyDesktopEffect()
|
|||
void BreezyDesktopEffect::reconfigure(ReconfigureFlags)
|
||||
{
|
||||
BreezyDesktopConfig::self()->read();
|
||||
setDisplayDistance(BreezyDesktopConfig::displayDistance());
|
||||
setFocusedDisplayDistance(BreezyDesktopConfig::focusedDisplayDistance());
|
||||
setAllDisplaysDistance(BreezyDesktopConfig::allDisplaysDistance());
|
||||
}
|
||||
|
||||
QVariantMap BreezyDesktopEffect::initialProperties(Output *screen)
|
||||
|
|
@ -205,13 +206,24 @@ QList<quint32> BreezyDesktopEffect::displayResolution() const {
|
|||
return m_displayResolution;
|
||||
}
|
||||
|
||||
qreal BreezyDesktopEffect::displayDistance() const {
|
||||
return m_displayDistance;
|
||||
qreal BreezyDesktopEffect::focusedDisplayDistance() const {
|
||||
return m_focusedDisplayDistance;
|
||||
}
|
||||
|
||||
void BreezyDesktopEffect::setDisplayDistance(qreal distance) {
|
||||
if (distance != m_displayDistance) {
|
||||
m_displayDistance = std::clamp(distance, 0.2, 2.5);
|
||||
void BreezyDesktopEffect::setFocusedDisplayDistance(qreal distance) {
|
||||
if (distance != m_focusedDisplayDistance) {
|
||||
m_focusedDisplayDistance = std::clamp(distance, 0.2, 2.5);
|
||||
Q_EMIT displayDistanceChanged();
|
||||
}
|
||||
}
|
||||
|
||||
qreal BreezyDesktopEffect::allDisplaysDistance() const {
|
||||
return m_allDisplaysDistance;
|
||||
}
|
||||
|
||||
void BreezyDesktopEffect::setAllDisplaysDistance(qreal distance) {
|
||||
if (distance != m_allDisplaysDistance) {
|
||||
m_allDisplaysDistance = std::clamp(distance, 0.2, 2.5);
|
||||
Q_EMIT displayDistanceChanged();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ namespace KWin
|
|||
Q_PROPERTY(QPointF cursorPos READ cursorPos NOTIFY cursorPosChanged)
|
||||
Q_PROPERTY(QList<qreal> lookAheadConfig READ lookAheadConfig NOTIFY devicePropertiesChanged)
|
||||
Q_PROPERTY(QList<quint32> displayResolution READ displayResolution NOTIFY devicePropertiesChanged)
|
||||
Q_PROPERTY(qreal displayDistance READ displayDistance NOTIFY displayDistanceChanged)
|
||||
Q_PROPERTY(qreal focusedDisplayDistance READ focusedDisplayDistance NOTIFY displayDistanceChanged)
|
||||
Q_PROPERTY(qreal allDisplaysDistance READ allDisplaysDistance NOTIFY displayDistanceChanged)
|
||||
Q_PROPERTY(qreal diagonalFOV READ diagonalFOV NOTIFY devicePropertiesChanged)
|
||||
Q_PROPERTY(qreal lensDistanceRatio READ lensDistanceRatio NOTIFY devicePropertiesChanged)
|
||||
Q_PROPERTY(bool sbsEnabled READ sbsEnabled NOTIFY devicePropertiesChanged)
|
||||
|
|
@ -46,8 +47,10 @@ namespace KWin
|
|||
bool imuResetState() const;
|
||||
QList<qreal> lookAheadConfig() const;
|
||||
QList<quint32> displayResolution() const;
|
||||
qreal displayDistance() const;
|
||||
void setDisplayDistance(qreal distance);
|
||||
qreal focusedDisplayDistance() const;
|
||||
void setFocusedDisplayDistance(qreal distance);
|
||||
qreal allDisplaysDistance() const;
|
||||
void setAllDisplaysDistance(qreal distance);
|
||||
qreal diagonalFOV() const;
|
||||
qreal lensDistanceRatio() const;
|
||||
bool sbsEnabled() const;
|
||||
|
|
@ -101,7 +104,8 @@ namespace KWin
|
|||
QFileSystemWatcher *m_shmDirectoryWatcher = nullptr;
|
||||
QPointF m_cursorPos;
|
||||
QTimer *m_cursorUpdateTimer = nullptr;
|
||||
qreal m_displayDistance = 1.05;
|
||||
qreal m_focusedDisplayDistance = 0.85;
|
||||
qreal m_allDisplaysDistance = 1.05;
|
||||
};
|
||||
|
||||
} // namespace KWin
|
||||
|
|
|
|||
|
|
@ -23,14 +23,34 @@
|
|||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="labelDisplayDistance">
|
||||
<widget class="QLabel" name="labelFocusedDisplayDistance">
|
||||
<property name="text">
|
||||
<string>Display Distance:</string>
|
||||
<string>Focused Display Distance:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QDoubleSpinBox" name="kcfg_DisplayDistance">
|
||||
<widget class="QDoubleSpinBox" name="kcfg_FocusedDisplayDistance">
|
||||
<property name="minimum">
|
||||
<double>0.2</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>2.5</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.01</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="labelAllDisplaysDistance">
|
||||
<property name="text">
|
||||
<string>All Displays Distance:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="kcfg_AllDisplaysDistance">
|
||||
<property name="minimum">
|
||||
<double>0.2</double>
|
||||
</property>
|
||||
|
|
|
|||
|
|
@ -3,9 +3,14 @@ import QtQuick3D
|
|||
|
||||
|
||||
Node {
|
||||
id: breezyDesktop
|
||||
|
||||
property var viewportResolution: effect.displayResolution
|
||||
property var screens: root.screens
|
||||
property var fovDetails: root.fovDetails
|
||||
property var monitorPlacements: root.monitorPlacements
|
||||
property var imuRotations: effect.imuRotations
|
||||
property int focusedMonitorIndex: -1
|
||||
|
||||
// x value for placing the viewport in the middle of all screens
|
||||
property real screensXMid: {
|
||||
|
|
@ -39,7 +44,6 @@ Node {
|
|||
id: displays
|
||||
}
|
||||
|
||||
|
||||
Repeater3D {
|
||||
model: screens.length
|
||||
delegate: BreezyDesktopDisplay {
|
||||
|
|
@ -60,13 +64,40 @@ Node {
|
|||
eulerRotation.y: screenRotationY
|
||||
eulerRotation.x: screenRotationX
|
||||
position: {
|
||||
// rotate about the Y (up) axis, to create a horizontal movement
|
||||
const transform = Qt.matrix4x4();
|
||||
transform.rotate(screenRotationY, Qt.vector3d(0, 1, 0));
|
||||
transform.rotate(screenRotationX, Qt.vector3d(1, 0, 0));
|
||||
|
||||
// camera looks along the negative Z axis
|
||||
return transform.times(Qt.vector3d(-monitorPlacement.centerNoRotate[1], monitorPlacement.centerNoRotate[2], -monitorPlacement.centerNoRotate[0]));
|
||||
let positionVector = displays.nwuToEusVector(monitorPlacement.centerNoRotate);
|
||||
if (focusedMonitorIndex === index) {
|
||||
positionVector = positionVector.times(effect.focusedDisplayDistance / effect.allDisplaysDistance);
|
||||
}
|
||||
|
||||
// position vector is only translated in flat directions, without rotations applied, so apply them here
|
||||
const rotationMatrix = Qt.matrix4x4();
|
||||
|
||||
// only one of these should ever be non-zero, since we only rotate in the direction of the "wrap" preference
|
||||
rotationMatrix.rotate(screenRotationY, Qt.vector3d(0, 1, 0));
|
||||
rotationMatrix.rotate(screenRotationX, Qt.vector3d(1, 0, 0));
|
||||
|
||||
return rotationMatrix.times(positionVector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FrameAnimation {
|
||||
running: true
|
||||
onTriggered: {
|
||||
if (breezyDesktop.imuRotations && breezyDesktop.imuRotations.length > 0) {
|
||||
const focusedIndex = displays.findFocusedMonitor(
|
||||
displays.eusToNwuQuat(breezyDesktop.imuRotations[0]),
|
||||
breezyDesktop.monitorPlacements.map(monitorVectors => monitorVectors.centerLook),
|
||||
breezyDesktop.focusedMonitorIndex,
|
||||
false, // TODO smooth follow
|
||||
breezyDesktop.fovDetails,
|
||||
breezyDesktop.screens.map(screen => screen.geometry)
|
||||
);
|
||||
|
||||
console.log(`\t\t\tBreezy - Next focused monitor index: ${focusedIndex}`);
|
||||
if (focusedIndex !== breezyDesktop.focusedMonitorIndex)
|
||||
breezyDesktop.focusedMonitorIndex = focusedIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ Item {
|
|||
property real lensDistanceRatio: effect.lensDistanceRatio
|
||||
property bool sbsEnabled: effect.sbsEnabled
|
||||
property bool customBannerEnabled: effect.customBannerEnabled
|
||||
property bool useImuRotation: true // Set to true to use XR rotation when available
|
||||
|
||||
implicitWidth: parent.width
|
||||
implicitHeight: parent.height
|
||||
|
|
@ -72,7 +71,7 @@ Item {
|
|||
FrameAnimation {
|
||||
running: true
|
||||
onTriggered: {
|
||||
if (root.useImuRotation && root.imuRotations && root.imuRotations.length > 0) {
|
||||
if (root.imuRotations && root.imuRotations.length > 0) {
|
||||
updateCamera(applyLookAhead(
|
||||
root.imuRotations[0],
|
||||
root.imuRotations[1],
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
property real displayDistance: effect.displayDistance
|
||||
readonly property real focusThreshold: 0.95 / 2.0
|
||||
readonly property real unfocusThreshold: 1.1 / 2.0
|
||||
|
||||
// Converts degrees to radians
|
||||
function degreeToRadian(degree) {
|
||||
|
|
@ -12,6 +13,16 @@ QtObject {
|
|||
return radian * 180 / Math.PI;
|
||||
}
|
||||
|
||||
function nwuToEusVector(vector) {
|
||||
// Converts NWU vector to EUS vector
|
||||
return Qt.vector3d(-vector.y, vector.z, -vector.x);
|
||||
}
|
||||
|
||||
function eusToNwuQuat(quaternion) {
|
||||
// Converts EUS quaternion to NWU quaternion
|
||||
return Qt.quaternion(quaternion.scalar, -quaternion.z, -quaternion.x, quaternion.y);
|
||||
}
|
||||
|
||||
// Converts diagonal FOV in radians and aspect ratio to horizontal and vertical FOVs
|
||||
function diagonalToCrossFOVs(diagonalFOVRadians, aspectRatio) {
|
||||
var flatDiagonalFOV = 2 * Math.tan(diagonalFOVRadians / 2);
|
||||
|
|
@ -24,10 +35,6 @@ QtObject {
|
|||
}
|
||||
}
|
||||
|
||||
function displayDistanceDefault() {
|
||||
return displayDistance;
|
||||
}
|
||||
|
||||
function actualWrapScheme(screens, viewportWidth, viewportHeight) {
|
||||
const minX = Math.min(...screens.map(screen => screen.geometry.x));
|
||||
const maxX = Math.max(...screens.map(screen => screen.geometry.x + screen.geometry.width));
|
||||
|
|
@ -44,8 +51,8 @@ QtObject {
|
|||
function fovDetails(screens, viewportWidth, viewportHeight, viewportDiagonalFOV, lensDistanceRatio) {
|
||||
const aspect = viewportWidth / viewportHeight;
|
||||
const fovRadians = diagonalToCrossFOVs(degreeToRadian(viewportDiagonalFOV), aspect);
|
||||
const defaultDistanceVerticalRadians = 2 * Math.atan(Math.tan(fovRadians.vertical / 2) / displayDistanceDefault());
|
||||
const defaultDistanceHorizontalRadians = 2 * Math.atan(Math.tan(fovRadians.horizontal / 2) / displayDistanceDefault());
|
||||
const defaultDistanceVerticalRadians = 2 * Math.atan(Math.tan(fovRadians.vertical / 2) / effect.allDisplaysDistance);
|
||||
const defaultDistanceHorizontalRadians = 2 * Math.atan(Math.tan(fovRadians.horizontal / 2) / effect.allDisplaysDistance);
|
||||
|
||||
// distance needed for the FOV-sized monitor to fill up the screen
|
||||
const fullScreenDistance = viewportHeight / 2 / Math.tan(fovRadians.vertical / 2);
|
||||
|
|
@ -106,26 +113,6 @@ QtObject {
|
|||
}
|
||||
})
|
||||
|
||||
// Applies a quaternion to a 3D vector
|
||||
function applyQuaternionToVector(vector, quaternion) {
|
||||
var t = [
|
||||
2.0 * (quaternion[1] * vector[2] - quaternion[2] * vector[1]),
|
||||
2.0 * (quaternion[2] * vector[0] - quaternion[0] * vector[2]),
|
||||
2.0 * (quaternion[0] * vector[1] - quaternion[1] * vector[0])
|
||||
];
|
||||
return [
|
||||
vector[0] + quaternion[3] * t[0] + quaternion[1] * t[2] - quaternion[2] * t[1],
|
||||
vector[1] + quaternion[3] * t[1] + quaternion[2] * t[0] - quaternion[0] * t[2],
|
||||
vector[2] + quaternion[3] * t[2] + quaternion[0] * t[1] - quaternion[1] * t[0]
|
||||
];
|
||||
}
|
||||
|
||||
// Normalizes a 3D vector
|
||||
function normalizeVector(vector) {
|
||||
var length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]);
|
||||
return [vector[0] / length, vector[1] / length, vector[2] / length];
|
||||
}
|
||||
|
||||
function monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorBeginPixel, monitorLengthPixels, lengthToRadianFn) {
|
||||
var closestWrapPixel = monitorBeginPixel;
|
||||
var closestWrap = cachedMonitorRadians[monitorBeginPixel];
|
||||
|
|
@ -227,16 +214,16 @@ QtObject {
|
|||
|
||||
monitorPlacements.push({
|
||||
originalIndex: originalIndex,
|
||||
centerNoRotate: [
|
||||
centerNoRotate: Qt.vector3d(
|
||||
monitorCenterRadius,
|
||||
0,
|
||||
upCenterPixels
|
||||
],
|
||||
centerLook: normalizeVector([
|
||||
),
|
||||
centerLook: Qt.vector3d(
|
||||
monitorCenterRadius * Math.cos(monitorWrapDetails.center),
|
||||
-monitorCenterRadius * Math.sin(monitorWrapDetails.center),
|
||||
upCenterPixels
|
||||
]),
|
||||
).normalized(),
|
||||
rotationAngleRadians: {
|
||||
x: 0,
|
||||
y: -monitorWrapDetails.center
|
||||
|
|
@ -267,16 +254,16 @@ QtObject {
|
|||
|
||||
monitorPlacements.push({
|
||||
originalIndex: originalIndex,
|
||||
centerNoRotate: [
|
||||
centerNoRotate: Qt.vector3d(
|
||||
monitorCenterRadius,
|
||||
westCenterPixels,
|
||||
0
|
||||
],
|
||||
centerLook: normalizeVector([
|
||||
),
|
||||
centerLook: Qt.vector3d(
|
||||
monitorCenterRadius * Math.cos(monitorWrapDetails.center),
|
||||
westCenterPixels,
|
||||
-monitorCenterRadius * Math.sin(monitorWrapDetails.center)
|
||||
]),
|
||||
).normalized(),
|
||||
rotationAngleRadians: {
|
||||
x: -monitorWrapDetails.center,
|
||||
y: 0
|
||||
|
|
@ -295,16 +282,16 @@ QtObject {
|
|||
|
||||
monitorPlacements.push({
|
||||
originalIndex: index,
|
||||
centerNoRotate: [
|
||||
centerNoRotate: Qt.vector3d(
|
||||
fovDetails.completeScreenDistancePixels,
|
||||
westCenterPixels,
|
||||
upCenterPixels
|
||||
],
|
||||
centerLook: normalizeVector([
|
||||
),
|
||||
centerLook: Qt.vector3d(
|
||||
fovDetails.completeScreenDistancePixels,
|
||||
westCenterPixels,
|
||||
upCenterPixels
|
||||
]),
|
||||
).normalized(),
|
||||
rotationAngleRadians: {
|
||||
x: 0,
|
||||
y: 0
|
||||
|
|
@ -313,7 +300,108 @@ QtObject {
|
|||
});
|
||||
}
|
||||
|
||||
// put them back in the original monitor order before returning
|
||||
monitorPlacements.sort(function(a, b) { return a.originalIndex - b.originalIndex; });
|
||||
|
||||
return monitorPlacements;
|
||||
}
|
||||
|
||||
// returns how far the look vector is from the center of the monitor, as a percentage of the monitor's width
|
||||
function getMonitorDistance(fovDetails, lookUpPixels, lookWestPixels, monitorVector, monitorDetails, upAngleToLength, westAngleToLength) {
|
||||
var monitorAspectRatio = monitorDetails.width / monitorDetails.height;
|
||||
|
||||
// weight the up distance by the aspect ratio
|
||||
var vectorUpPixels = upAngleToLength(
|
||||
fovDetails.defaultDistanceVerticalRadians,
|
||||
fovDetails.heightPixels,
|
||||
fovDetails.completeScreenDistancePixels,
|
||||
monitorVector.z,
|
||||
monitorVector.x
|
||||
);
|
||||
var upDeltaPixels = (lookUpPixels - vectorUpPixels) * monitorAspectRatio;
|
||||
|
||||
var vectorWestPixels = westAngleToLength(
|
||||
fovDetails.defaultDistanceHorizontalRadians,
|
||||
fovDetails.widthPixels,
|
||||
fovDetails.completeScreenDistancePixels,
|
||||
monitorVector.y,
|
||||
monitorVector.x
|
||||
);
|
||||
var westDeltaPixels = lookWestPixels - vectorWestPixels;
|
||||
var totalDeltaPixels = Math.sqrt(upDeltaPixels * upDeltaPixels + westDeltaPixels * westDeltaPixels);
|
||||
|
||||
// threshold is a percentage of width, and height was already properly weighted
|
||||
return totalDeltaPixels / monitorDetails.width;
|
||||
}
|
||||
|
||||
function findFocusedMonitor(quaternion, monitorVectors, currentFocusedIndex, smoothFollowEnabled, fovDetails, monitorsDetails) {
|
||||
var lookVector = Qt.vector3d(1.0, 0.0, 0.0); // NWU vector pointing to the center of the screen
|
||||
var rotatedLookVector = quaternion.times(lookVector);
|
||||
|
||||
// Use curved or flat conversion functions depending on wrapping scheme
|
||||
var upConversionFns = fovDetails.monitorWrappingScheme === "vertical" ? fovConversionFns.curved : fovConversionFns.flat;
|
||||
var lookUpPixels = upConversionFns.angleToLength(
|
||||
fovDetails.defaultDistanceVerticalRadians,
|
||||
fovDetails.heightPixels,
|
||||
fovDetails.completeScreenDistancePixels,
|
||||
rotatedLookVector.z,
|
||||
rotatedLookVector.x
|
||||
);
|
||||
var westConversionFns = fovDetails.monitorWrappingScheme === "horizontal" ? fovConversionFns.curved : fovConversionFns.flat;
|
||||
var lookWestPixels = westConversionFns.angleToLength(
|
||||
fovDetails.defaultDistanceHorizontalRadians,
|
||||
fovDetails.widthPixels,
|
||||
fovDetails.completeScreenDistancePixels,
|
||||
rotatedLookVector.y,
|
||||
rotatedLookVector.x
|
||||
);
|
||||
|
||||
var closestIndex = -1;
|
||||
var closestDistance = Number.POSITIVE_INFINITY;
|
||||
|
||||
// Check current focused monitor first
|
||||
if (currentFocusedIndex !== -1) {
|
||||
var focusedDistance = getMonitorDistance(
|
||||
fovDetails,
|
||||
lookUpPixels,
|
||||
lookWestPixels,
|
||||
monitorVectors[currentFocusedIndex],
|
||||
monitorsDetails[currentFocusedIndex],
|
||||
upConversionFns.angleToLength,
|
||||
westConversionFns.angleToLength
|
||||
) * effect.focusedDisplayDistance / effect.allDisplaysDistance;
|
||||
|
||||
console.log(`\t\t\tBreezy - Focused monitor index: ${currentFocusedIndex}, distance: ${focusedDistance}`);
|
||||
|
||||
if (smoothFollowEnabled || focusedDistance < unfocusThreshold)
|
||||
return currentFocusedIndex;
|
||||
}
|
||||
|
||||
// Find the closest monitor
|
||||
for (var i = 0; i < monitorVectors.length; ++i) {
|
||||
if (i === currentFocusedIndex)
|
||||
continue;
|
||||
var distance = getMonitorDistance(
|
||||
fovDetails,
|
||||
lookUpPixels,
|
||||
lookWestPixels,
|
||||
monitorVectors[i],
|
||||
monitorsDetails[i],
|
||||
upConversionFns.angleToLength,
|
||||
westConversionFns.angleToLength
|
||||
);
|
||||
console.log(`\t\t\tBreezy - Monitor index: ${i}, distance: ${distance}`);
|
||||
|
||||
if (distance < closestDistance) {
|
||||
closestIndex = i;
|
||||
closestDistance = distance;
|
||||
}
|
||||
}
|
||||
|
||||
if (smoothFollowEnabled || closestDistance < focusThreshold)
|
||||
return closestIndex;
|
||||
|
||||
// Unfocus all displays
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -33,8 +33,9 @@ Item {
|
|||
id: displays
|
||||
}
|
||||
|
||||
property var fovDetails: displays.fovDetails(screens, viewportResolution[0], viewportResolution[1], viewportDiagonalFOVDegrees, effect.lensDistanceRatio)
|
||||
|
||||
property var monitorPlacements: {
|
||||
const fovDetails = displays.fovDetails(screens, viewportResolution[0], viewportResolution[1], viewportDiagonalFOVDegrees, effect.lensDistanceRatio);
|
||||
const monitorSpacing = 0.0;
|
||||
return displays.monitorsToPlacements(fovDetails, screens.map(screen => screen.geometry), monitorSpacing);
|
||||
}
|
||||
|
|
@ -58,7 +59,6 @@ Item {
|
|||
|
||||
PerspectiveCamera {
|
||||
id: camera
|
||||
frustumCullingEnabled: false
|
||||
}
|
||||
|
||||
BreezyDesktop {
|
||||
|
|
|
|||
Loading…
Reference in New Issue