diff --git a/.gitmodules b/.gitmodules index e7a2906..a9ce0e4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,9 @@ [submodule "ui/modules/PyXRLinuxDriverIPC"] path = ui/modules/PyXRLinuxDriverIPC url = https://github.com/wheaney/PyXRLinuxDriverIPC.git +[submodule "gnome/src/shared"] + path = gnome/src/shared + url = git@github.com:wheaney/breezy-desktop-shared-js.git +[submodule "kwin/src/qml/shared"] + path = kwin/src/qml/shared + url = git@github.com:wheaney/breezy-desktop-shared-js.git diff --git a/gnome/bin/package_extension b/gnome/bin/package_extension index 346c2ad..6bda9ca 100755 --- a/gnome/bin/package_extension +++ b/gnome/bin/package_extension @@ -41,6 +41,7 @@ copy_static_files() { cp $SRC_DIR/dbus-interfaces/*.xml $DEST_DIR/dbus-interfaces/ mkdir -p $DEST_DIR/textures/ cp -rL $SRC_DIR/textures/* $DEST_DIR/textures/ + cp -r $SRC_DIR/shared $DEST_DIR/shared cp $SRC_DIR/metadata.json $DEST_DIR/ } diff --git a/gnome/src/math.js b/gnome/src/math.js deleted file mode 100644 index e5af812..0000000 --- a/gnome/src/math.js +++ /dev/null @@ -1 +0,0 @@ -export { degreeToRadian, diagonalToCrossFOVs, fovConversionFns, applyQuaternionToVector, vectorMagnitude, normalizeVector } from '../../shared/js/math.js'; diff --git a/gnome/src/shared b/gnome/src/shared new file mode 160000 index 0000000..6cc1d97 --- /dev/null +++ b/gnome/src/shared @@ -0,0 +1 @@ +Subproject commit 6cc1d97ece687b2d71dfdb51c94eaf11c82c7fe5 diff --git a/gnome/src/virtualdisplayeffect.js b/gnome/src/virtualdisplayeffect.js index 7705842..ed6310b 100644 --- a/gnome/src/virtualdisplayeffect.js +++ b/gnome/src/virtualdisplayeffect.js @@ -5,7 +5,7 @@ import GObject from 'gi://GObject'; import Shell from 'gi://Shell'; import Globals from './globals.js'; -import { degreeToRadian, diagonalToCrossFOVs, fovConversionFns } from './math.js'; +import { degreeToRadian, diagonalToCrossFOVs, fovConversionFns } from './shared/math.js'; // these need to mirror the values in XRLinuxDriver diff --git a/gnome/src/virtualdisplaysactor.js b/gnome/src/virtualdisplaysactor.js index da71959..c70e3dd 100644 --- a/gnome/src/virtualdisplaysactor.js +++ b/gnome/src/virtualdisplaysactor.js @@ -7,8 +7,8 @@ import Shell from 'gi://Shell'; import St from 'gi://St'; import { VirtualDisplayEffect, SMOOTH_FOLLOW_SLERP_TIMELINE_MS } from './virtualdisplayeffect.js'; -import { degreeToRadian, diagonalToCrossFOVs, fovConversionFns } from './math.js'; -import { findFocusedMonitor, monitorsToPlacements } from '../../shared/js/displayPlacement.js'; +import { degreeToRadian, diagonalToCrossFOVs, fovConversionFns } from './shared/math.js'; +import { findFocusedMonitor, monitorsToPlacements } from './shared/displayPlacement.js'; import * as Main from 'resource:///org/gnome/shell/ui/main.js'; diff --git a/kwin/src/qml/Displays.qml b/kwin/src/qml/Displays.qml index c9a6e8f..3106a13 100644 --- a/kwin/src/qml/Displays.qml +++ b/kwin/src/qml/Displays.qml @@ -1,6 +1,6 @@ import QtQuick -import "../../../shared/js/math.js" as SharedMath -import "../../../shared/js/displayPlacement.js" as SharedPlacement +import "./shared/math.js" as SharedMath +import "./shared/displayPlacement.js" as SharedPlacement QtObject { readonly property real focusThreshold: SharedPlacement.FOCUS_THRESHOLD diff --git a/kwin/src/qml/shared b/kwin/src/qml/shared new file mode 160000 index 0000000..6cc1d97 --- /dev/null +++ b/kwin/src/qml/shared @@ -0,0 +1 @@ +Subproject commit 6cc1d97ece687b2d71dfdb51c94eaf11c82c7fe5 diff --git a/shared/js/displayPlacement.js b/shared/js/displayPlacement.js deleted file mode 100644 index 078784d..0000000 --- a/shared/js/displayPlacement.js +++ /dev/null @@ -1,365 +0,0 @@ -import { applyQuaternionToVector, fovConversionFns, vectorMagnitude } from './math.js'; - -// if nothing is in focus, take it as soon as it crosses into the monitor's bounds -export const FOCUS_THRESHOLD = 0.95 / 2.0; - -// if we leave the monitor with some margin, unfocus even if no other monitor is in focus -export const UNFOCUS_THRESHOLD = 1.1 / 2.0; - -/** - * Given the known radian positions of previously-placed monitors, compute the begin/center/end - * radian positions for one monitor along a wrapped axis. - * - * All vector arguments use plain JS arrays; callers on Qt platforms convert before/after. - * - * @param {Object} cachedMonitorRadians - mutable pixel→radian cache shared across all monitors in one axis - * @param {number} monitorSpacingPixels - * @param {number} monitorBeginPixel - * @param {number} monitorLengthPixels - * @param {function} lengthToRadianFn - * @returns {{begin: number, center: number, end: number}} - */ -export function monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorBeginPixel, monitorLengthPixels, lengthToRadianFn) { - // Monitor coordinates can become fractional due to size adjustment. - // If a monitor edge lands extremely close to a cached pixel key, snap to it; - // otherwise tiny negative gaps can cause us to subtract a full spacing interval. - let beginPixel = monitorBeginPixel; - const pixelEpsilon = Math.max(1e-6, Math.abs(monitorLengthPixels) * 1e-6); - - let closestWrapPixel = beginPixel; - let closestWrap = cachedMonitorRadians[beginPixel]; - if (closestWrap === undefined) { - closestWrapPixel = Object.keys(cachedMonitorRadians).reduce((previousPixel, currentPixel) => { - if (previousPixel === undefined) return currentPixel; - - const currentDelta = currentPixel - monitorBeginPixel; - const previousDelta = previousPixel - monitorBeginPixel; - - // always prefer an exact monitor width match - if (previousDelta % monitorLengthPixels !== 0) { - if (currentDelta % monitorLengthPixels === 0) return currentPixel; - - // prefer placing a monitor to the right or below, even if there's a closer placement to the left or above - if (previousDelta < 0 && currentDelta > 0) return currentPixel; - - // otherwise, just prefer the closest one - if (Math.abs(currentDelta) < Math.abs(previousDelta)) return currentPixel; - } - - return previousPixel; - }, undefined); - closestWrap = cachedMonitorRadians[closestWrapPixel]; - } - - const closestWrapPixelNumber = Number(closestWrapPixel); - if (Number.isFinite(closestWrapPixelNumber) && Math.abs(closestWrapPixelNumber - beginPixel) < pixelEpsilon) { - beginPixel = closestWrapPixelNumber; - closestWrapPixel = closestWrapPixelNumber; - } - - const spacingRadians = lengthToRadianFn(monitorSpacingPixels); - if (closestWrapPixel !== beginPixel) { - // there's a gap between the cached wrap value and this one - const gapPixels = beginPixel - closestWrapPixel; - const gapRadians = lengthToRadianFn(gapPixels); - - // use Math.floor so if it's negative (this monitor is to the left of or above the closest) it will always - // compensate for the spacing that's needed at the right/bottom - const appliedSpacingRadians = Math.floor(gapPixels / monitorLengthPixels) * spacingRadians; - - closestWrap = closestWrap + gapRadians + appliedSpacingRadians; - closestWrapPixel = beginPixel; - cachedMonitorRadians[closestWrapPixel] = closestWrap; - } - - const monitorRadians = lengthToRadianFn(monitorLengthPixels); - const centerRadians = closestWrap + monitorRadians / 2; - const endRadians = closestWrap + monitorRadians; - - // cache the end position so adjacent monitors can snap to it - const nextMonitorPixel = beginPixel + monitorLengthPixels; - if (cachedMonitorRadians[nextMonitorPixel] === undefined) - cachedMonitorRadians[nextMonitorPixel] = endRadians + spacingRadians; - - return { - begin: closestWrap, - center: centerRadians, - end: endRadians - } -} - -// sort monitors left-to-right, top-to-bottom before placing them to avoid odd gaps -export function horizontalMonitorSort(monitors) { - return monitors.map((monitor, index) => ({originalIndex: index, monitorDetails: monitor})).sort((a, b) => { - const aMon = a.monitorDetails; - const bMon = b.monitorDetails; - if (aMon.y !== bMon.y) return aMon.y - bMon.y; - return aMon.x - bMon.x; - }); -} - -// sort monitors top-to-bottom, left-to-right before placing them to avoid odd gaps -export function verticalMonitorSort(monitors) { - return monitors.map((monitor, index) => ({originalIndex: index, monitorDetails: monitor})).sort((a, b) => { - const aMon = a.monitorDetails; - const bMon = b.monitorDetails; - if (aMon.x !== bMon.x) return aMon.x - bMon.x; - return aMon.y - bMon.y; - }); -} - -/** - * Detect whether a multi-monitor layout is wider or taller relative to the viewport, - * returning 'horizontal' or 'vertical'. Used when wrappingScheme is 'automatic'. - * - * @param {Object[]} monitors - [{x, y, width, height}] - * @param {number} viewportWidth - * @param {number} viewportHeight - * @returns {'horizontal'|'vertical'} - */ -export function autoDetectWrapScheme(monitors, viewportWidth, viewportHeight) { - const minX = Math.min(...monitors.map(m => m.x)); - const maxX = Math.max(...monitors.map(m => m.x + m.width)); - const minY = Math.min(...monitors.map(m => m.y)); - const maxY = Math.max(...monitors.map(m => m.y + m.height)); - return (maxX - minX) / viewportWidth >= (maxY - minY) / viewportHeight ? 'horizontal' : 'vertical'; -} - -/** - * Returns how far the look vector is from the center of a monitor, as a percentage of - * the monitor's dimensions (0 = center, 0.5 = exactly at edge, >0.5 = outside). - * - * All vector arguments are plain arrays in NWU order: [north, west, up]. - * - * @param {Object} fovDetails - * @param {number} lookUpPixels - * @param {number} lookWestPixels - * @param {number[]} monitorVector - [north, west, up] center of the monitor relative to lens - * @param {{width: number, height: number}} monitorDetails - * @param {function} upAngleToLength - * @param {function} westAngleToLength - * @returns {number} - */ -export function getMonitorDistance(fovDetails, lookUpPixels, lookWestPixels, monitorVector, monitorDetails, upAngleToLength, westAngleToLength) { - const monitorDistance = vectorMagnitude(monitorVector); - const distanceAdjustment = monitorDistance / fovDetails.completeScreenDistancePixels; - - // monitorVector[0]=north, monitorVector[1]=west, monitorVector[2]=up - const vectorUpPixels = upAngleToLength( - fovDetails.defaultDistanceVerticalRadians, - fovDetails.heightPixels, - monitorDistance, - monitorVector[2], - monitorVector[0] - ) * distanceAdjustment; - const upPercentage = Math.abs(lookUpPixels * distanceAdjustment - vectorUpPixels) / monitorDetails.height; - - const vectorWestPixels = westAngleToLength( - fovDetails.defaultDistanceHorizontalRadians, - fovDetails.widthPixels, - monitorDistance, - monitorVector[1], - monitorVector[0] - ) * distanceAdjustment; - const westPercentage = Math.abs(lookWestPixels * distanceAdjustment - vectorWestPixels) / monitorDetails.width; - - return Math.max(upPercentage, westPercentage); -} - -/** - * Find which monitor the user is looking at. - * - * All vectors use plain NWU arrays: [north, west, up]. - * Quaternion is [x, y, z, w]. - * - * @param {number[]} quaternion - current head orientation [x, y, z, w] - * @param {number[]} position - lens position [north, west, up] in pixel units - * @param {number[][]} monitorVectors - centerLook for each monitor - * @param {number} currentFocusedIndex - * @param {number} focusedMonitorDistance - display_distance / display_distance_default, < 1 when zoomed in - * @param {boolean} smoothFollowEnabled - * @param {Object} fovDetails - * @param {Object[]} monitorsDetails - [{x, y, width, height}] - * @returns {number} index of focused monitor, or -1 if none - */ -export function findFocusedMonitor(quaternion, position, monitorVectors, currentFocusedIndex, focusedMonitorDistance, smoothFollowEnabled, fovDetails, monitorsDetails) { - if (currentFocusedIndex !== -1 && smoothFollowEnabled) return currentFocusedIndex; - - const lookVector = [1.0, 0.0, 0.0]; // NWU vector pointing to the center of the screen - const rotatedLookVector = applyQuaternionToVector(lookVector, quaternion); - - // TODO - right now we're using the curved functions to figure out distances even for flat monitors - // because it will account for the monitors facing towards us, but this will lose some accuracy - const upConversionFns = fovDetails.monitorWrappingScheme === 'vertical' ? fovConversionFns.curved : fovConversionFns.flat; - const lookUpPixels = upConversionFns.angleToLength( - fovDetails.defaultDistanceVerticalRadians, - fovDetails.heightPixels, - fovDetails.completeScreenDistancePixels, - rotatedLookVector[2], - rotatedLookVector[0] - ); - const westConversionFns = fovDetails.monitorWrappingScheme === 'horizontal' ? fovConversionFns.curved : fovConversionFns.flat; - const lookWestPixels = westConversionFns.angleToLength( - fovDetails.defaultDistanceHorizontalRadians, - fovDetails.widthPixels, - fovDetails.completeScreenDistancePixels, - rotatedLookVector[1], - rotatedLookVector[0] - ); - - function vectorRelativeToLensPosition(vector) { - return [ - vector[0] - position[0], - vector[1] - position[1], - vector[2] - position[2] - ]; - } - - // the currently focused monitor is the most likely to be the closest, check it first and exit early if it is - if (currentFocusedIndex !== -1) { - const focusedDistance = getMonitorDistance( - fovDetails, - lookUpPixels, - lookWestPixels, - vectorRelativeToLensPosition(monitorVectors[currentFocusedIndex]), - monitorsDetails[currentFocusedIndex], - upConversionFns.angleToLength, - westConversionFns.angleToLength - ) * focusedMonitorDistance; - - if (focusedDistance < UNFOCUS_THRESHOLD) return currentFocusedIndex; - } - - let closestIndex = -1; - let closestDistance = Infinity; - - monitorVectors.forEach((monitorVector, index) => { - if (index === currentFocusedIndex) return; - - const distance = getMonitorDistance( - fovDetails, - lookUpPixels, - lookWestPixels, - vectorRelativeToLensPosition(monitorVector), - monitorsDetails[index], - upConversionFns.angleToLength, - westConversionFns.angleToLength - ); - - if (distance < closestDistance) { - closestIndex = index; - closestDistance = distance; - } - }); - - if (smoothFollowEnabled || closestDistance < FOCUS_THRESHOLD) return closestIndex; - - return -1; -} - -/** - * Convert monitor layout details into NWU placement vectors for rendering. - * - * Vectors in the returned objects are plain arrays [north, west, up]. - * Qt callers should wrap centerNoRotate/centerLook with Qt.vector3d after calling. - * - * @param {Object} fovDetails - widthPixels, heightPixels, sizeAdjustedWidthPixels, sizeAdjustedHeightPixels, - * defaultDistanceHorizontalRadians, defaultDistanceVerticalRadians, - * completeScreenDistancePixels, monitorWrappingScheme, curvedDisplay - * @param {Object[]} monitorDetailsList - [{x, y, width, height}] in size-adjusted viewport-relative coords - * @param {number} monitorSpacing - visual spacing as a fraction of viewport width (e.g. 0.02) - * @returns {Object[]} - [{originalIndex, monitorCenterNorth, centerNoRotate, centerLook, rotationAngleRadians}] - */ -export function monitorsToPlacements(fovDetails, monitorDetailsList, monitorSpacing) { - const monitorPlacements = []; - const cachedMonitorRadians = {}; - - const conversionFns = fovDetails.curvedDisplay ? fovConversionFns.curved : fovConversionFns.flat; - - if (fovDetails.monitorWrappingScheme === 'horizontal') { - const sideEdgeRadius = conversionFns.centerToFovEdgeDistance(fovDetails.completeScreenDistancePixels, fovDetails.sizeAdjustedWidthPixels); - const monitorSpacingPixels = monitorSpacing * fovDetails.sizeAdjustedWidthPixels; - - const lengthToRadianFn = (targetWidth) => conversionFns.lengthToRadians( - fovDetails.defaultDistanceHorizontalRadians, - fovDetails.widthPixels, - sideEdgeRadius, - targetWidth - ); - - cachedMonitorRadians[0] = -lengthToRadianFn(fovDetails.sizeAdjustedWidthPixels) / 2; - horizontalMonitorSort(monitorDetailsList).forEach(({monitorDetails, originalIndex}) => { - const monitorWrapDetails = monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorDetails.x, monitorDetails.width, lengthToRadianFn); - const monitorCenterRadius = conversionFns.fovEdgeToScreenCenterDistance(sideEdgeRadius, monitorDetails.width); - const upTopPixels = -monitorDetails.y - (monitorDetails.y / fovDetails.sizeAdjustedHeightPixels) * monitorSpacingPixels; - const upCenterOffsetPixels = (monitorDetails.height - fovDetails.sizeAdjustedHeightPixels) / 2; - const upCenterPixels = upTopPixels - upCenterOffsetPixels; - - monitorPlacements.push({ - originalIndex, - monitorCenterNorth: monitorCenterRadius, - centerNoRotate: [monitorCenterRadius, 0, upCenterPixels], - centerLook: [ - monitorCenterRadius * Math.cos(monitorWrapDetails.center), - -monitorCenterRadius * Math.sin(monitorWrapDetails.center), - upCenterPixels - ], - rotationAngleRadians: { x: 0, y: -monitorWrapDetails.center } - }); - }); - } else if (fovDetails.monitorWrappingScheme === 'vertical') { - const topEdgeRadius = conversionFns.centerToFovEdgeDistance(fovDetails.completeScreenDistancePixels, fovDetails.sizeAdjustedHeightPixels); - const monitorSpacingPixels = monitorSpacing * fovDetails.sizeAdjustedHeightPixels; - - const lengthToRadianFn = (targetHeight) => conversionFns.lengthToRadians( - fovDetails.defaultDistanceVerticalRadians, - fovDetails.heightPixels, - topEdgeRadius, - targetHeight - ); - - cachedMonitorRadians[0] = -lengthToRadianFn(fovDetails.sizeAdjustedHeightPixels) / 2; - verticalMonitorSort(monitorDetailsList).forEach(({monitorDetails, originalIndex}) => { - const monitorWrapDetails = monitorWrap(cachedMonitorRadians, monitorSpacingPixels, monitorDetails.y, monitorDetails.height, lengthToRadianFn); - const monitorCenterRadius = conversionFns.fovEdgeToScreenCenterDistance(topEdgeRadius, monitorDetails.height); - const westLeftPixels = -monitorDetails.x - (monitorDetails.x / fovDetails.sizeAdjustedWidthPixels) * monitorSpacingPixels; - const westCenterOffsetPixels = (monitorDetails.width - fovDetails.sizeAdjustedWidthPixels) / 2; - const westCenterPixels = westLeftPixels - westCenterOffsetPixels; - - monitorPlacements.push({ - originalIndex, - monitorCenterNorth: monitorCenterRadius, - centerNoRotate: [monitorCenterRadius, westCenterPixels, 0], - centerLook: [ - monitorCenterRadius * Math.cos(monitorWrapDetails.center), - westCenterPixels, - -monitorCenterRadius * Math.sin(monitorWrapDetails.center) - ], - rotationAngleRadians: { x: -monitorWrapDetails.center, y: 0 } - }); - }); - } else { - const monitorSpacingPixels = monitorSpacing * fovDetails.sizeAdjustedWidthPixels; - - monitorDetailsList.forEach((monitorDetails, index) => { - const upTopPixels = -monitorDetails.y - (monitorDetails.y / fovDetails.sizeAdjustedHeightPixels) * monitorSpacingPixels; - const westLeftPixels = -monitorDetails.x - (monitorDetails.x / fovDetails.sizeAdjustedWidthPixels) * monitorSpacingPixels; - const westCenterOffsetPixels = (monitorDetails.width - fovDetails.sizeAdjustedWidthPixels) / 2; - const upCenterOffsetPixels = (monitorDetails.height - fovDetails.sizeAdjustedHeightPixels) / 2; - const westCenterPixels = westLeftPixels - westCenterOffsetPixels; - const upCenterPixels = upTopPixels - upCenterOffsetPixels; - - monitorPlacements.push({ - originalIndex: index, - monitorCenterNorth: fovDetails.completeScreenDistancePixels, - centerNoRotate: [fovDetails.completeScreenDistancePixels, westCenterPixels, upCenterPixels], - centerLook: [fovDetails.completeScreenDistancePixels, westCenterPixels, upCenterPixels], - rotationAngleRadians: { x: 0, y: 0 } - }); - }); - } - - monitorPlacements.sort((a, b) => a.originalIndex - b.originalIndex); - - return monitorPlacements; -} diff --git a/shared/js/math.js b/shared/js/math.js deleted file mode 100644 index e04284f..0000000 --- a/shared/js/math.js +++ /dev/null @@ -1,79 +0,0 @@ -export function degreeToRadian(degree) { - return degree * Math.PI / 180; -} - -// FOV in radians is spherical, so doesn't follow Pythagoras' theorem -export function diagonalToCrossFOVs(diagonalFOVRadians, aspectRatio) { - // first convert from a spherical FOV to a diagonal FOV on a flat plane at a unit distance of 1.0 - const diagonalLengthUnitDistance = 2 * Math.tan(diagonalFOVRadians / 2); - - // then convert to flat plane horizontal and vertical FOVs - const heightUnitDistance = diagonalLengthUnitDistance / Math.sqrt(1 + aspectRatio * aspectRatio); - const widthUnitDistance = heightUnitDistance * aspectRatio; - - return { - // then convert back to spherical FOV - diagonalRadians: diagonalFOVRadians, - horizontalRadians: 2 * Math.atan(widthUnitDistance / 2), - verticalRadians: 2 * Math.atan(heightUnitDistance / 2), - - // flat values are relative to a unit distance of 1.0 - diagonalLengthUnitDistance, - widthUnitDistance, - heightUnitDistance - } -} - -const segmentsPerRadian = 20.0 / degreeToRadian(90.0); - -// displays are placed around a circle, these functions help determine radians and distances from the original -// FOV measurements scaled to the display dimensions -export const fovConversionFns = { - // convert curved FOV for flat displays - flat: { - // distance to an edge is the hypothenuse of the triangle where the opposite side is half the width of the reference fov screen - centerToFovEdgeDistance: (centerDistance, fovLength) => Math.sqrt(Math.pow(fovLength / 2, 2) + Math.pow(centerDistance, 2)), - fovEdgeToScreenCenterDistance: (edgeDistance, screenLength) => Math.sqrt(Math.pow(edgeDistance, 2) - Math.pow(screenLength / 2, 2)), - lengthToRadians: (fovRadians, fovLength, screenEdgeDistance, toLength) => Math.asin(toLength / 2 / screenEdgeDistance) * 2, - angleToLength: (fovRadians, fovLength, screenDistance, toAngleOpposite, toAngleAdjacent) => { - return toAngleOpposite / toAngleAdjacent * screenDistance; - }, - fovRadiansAtDistance: (fovRadians, unitLength, newScreenDistance) => { - return 2 * Math.atan(unitLength / 2 / newScreenDistance); - }, - radiansToSegments: (screenRadians) => 1 - }, - - // convert curved FOV for curved displays, scaling either involves no change or is linear - curved: { - centerToFovEdgeDistance: (centerDistance, fovLength) => centerDistance, - fovEdgeToScreenCenterDistance: (edgeDistance, screenLength) => edgeDistance, - lengthToRadians: (fovRadians, fovLength, screenEdgeDistance, toLength) => fovRadians / fovLength * toLength, - angleToLength: (fovRadians, fovLength, screenDistance, toAngleOpposite, toAngleAdjacent) => fovLength / fovRadians * Math.atan2(toAngleOpposite, toAngleAdjacent), - fovRadiansAtDistance: (fovRadians, unitLength, newScreenDistance) => fovRadians / newScreenDistance, - radiansToSegments: (screenRadians) => Math.ceil(screenRadians * segmentsPerRadian) - } -} - -// quaternion is [x, y, z, w], vector is [x, y, z] -export const applyQuaternionToVector = (vector, quaternion) => { - const 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] - ]; -} - -export const vectorMagnitude = (vector) => { - return Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]); -} - -export const normalizeVector = (vector) => { - const length = vectorMagnitude(vector); - return [vector[0] / length, vector[1] / length, vector[2] / length]; -}