forked from EXT/VR180-Web-Player
@@ -18,8 +18,20 @@ export type PalmAimRay = {
|
||||
origin: VectorLike;
|
||||
};
|
||||
|
||||
export type TimedPalmAimRay = PalmAimRay & {
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
export type PalmAimLatch = {
|
||||
isSelecting: boolean;
|
||||
selectedRay: TimedPalmAimRay | null;
|
||||
stableRay: TimedPalmAimRay | null;
|
||||
};
|
||||
|
||||
const MIN_AXIS_LENGTH_SQ = 0.000001;
|
||||
const PALM_AIM_FORWARD_TILT_DEGREES = 40;
|
||||
const PALM_SURFACE_OFFSET_METERS = 0.035;
|
||||
export const DEFAULT_PALM_AIM_LATCH_MAX_AGE_MS = 300;
|
||||
|
||||
export function computePalmAimRay(input: PalmAimInput): PalmAimRay | null {
|
||||
const { indexMetacarpal, pinkyMetacarpal, wrist } = input;
|
||||
@@ -43,7 +55,10 @@ export function computePalmAimRay(input: PalmAimInput): PalmAimRay | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
const direction = normalize(getPalmNormal(fingerAxis, acrossPalmAxis, input.handedness));
|
||||
const direction = normalize(getTiltedPalmDirection(
|
||||
getPalmNormal(fingerAxis, acrossPalmAxis, input.handedness),
|
||||
fingerAxis
|
||||
));
|
||||
if (!direction) {
|
||||
return null;
|
||||
}
|
||||
@@ -53,6 +68,71 @@ export function computePalmAimRay(input: PalmAimInput): PalmAimRay | null {
|
||||
return { direction, origin };
|
||||
}
|
||||
|
||||
export function createPalmAimLatch(): PalmAimLatch {
|
||||
return {
|
||||
isSelecting: false,
|
||||
selectedRay: null,
|
||||
stableRay: null
|
||||
};
|
||||
}
|
||||
|
||||
export function recordStablePalmAimRay(
|
||||
latch: PalmAimLatch | null | undefined,
|
||||
ray: PalmAimRay,
|
||||
timestamp: number
|
||||
): void {
|
||||
if (!latch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (latch.isSelecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
latch.stableRay = withTimestamp(ray, timestamp);
|
||||
}
|
||||
|
||||
export function beginPalmAimSelection(
|
||||
latch: PalmAimLatch | null | undefined,
|
||||
timestamp: number,
|
||||
maxAgeMs = DEFAULT_PALM_AIM_LATCH_MAX_AGE_MS
|
||||
): PalmAimRay | null {
|
||||
if (!latch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
latch.isSelecting = true;
|
||||
const stableRay = getFreshTimedRay(latch.stableRay, timestamp, maxAgeMs);
|
||||
latch.selectedRay = stableRay ? cloneTimedRay(stableRay) : null;
|
||||
return latch.selectedRay ? clonePalmAimRay(latch.selectedRay) : null;
|
||||
}
|
||||
|
||||
export function endPalmAimSelection(latch: PalmAimLatch | null | undefined): void {
|
||||
if (!latch) {
|
||||
return;
|
||||
}
|
||||
|
||||
latch.isSelecting = false;
|
||||
latch.selectedRay = null;
|
||||
}
|
||||
|
||||
export function getPalmAimSelectionRay(
|
||||
latch: PalmAimLatch | null | undefined,
|
||||
timestamp: number,
|
||||
maxAgeMs = DEFAULT_PALM_AIM_LATCH_MAX_AGE_MS
|
||||
): PalmAimRay | null {
|
||||
if (!latch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (latch.isSelecting && latch.selectedRay) {
|
||||
return clonePalmAimRay(latch.selectedRay);
|
||||
}
|
||||
|
||||
const stableRay = getFreshTimedRay(latch.stableRay, timestamp, maxAgeMs);
|
||||
return stableRay ? clonePalmAimRay(stableRay) : null;
|
||||
}
|
||||
|
||||
function getPalmNormal(fingerAxis: VectorLike, acrossPalmAxis: VectorLike, handedness?: string | null): VectorLike {
|
||||
if (handedness === 'left') {
|
||||
return cross(acrossPalmAxis, fingerAxis);
|
||||
@@ -61,6 +141,56 @@ function getPalmNormal(fingerAxis: VectorLike, acrossPalmAxis: VectorLike, hande
|
||||
return cross(fingerAxis, acrossPalmAxis);
|
||||
}
|
||||
|
||||
function getTiltedPalmDirection(palmNormal: VectorLike, fingerAxis: VectorLike): VectorLike {
|
||||
const tiltRadians = (PALM_AIM_FORWARD_TILT_DEGREES * Math.PI) / 180;
|
||||
return add(
|
||||
scale(palmNormal, Math.cos(tiltRadians)),
|
||||
scale(fingerAxis, Math.sin(tiltRadians))
|
||||
);
|
||||
}
|
||||
|
||||
function withTimestamp(ray: PalmAimRay, timestamp: number): TimedPalmAimRay {
|
||||
return {
|
||||
...clonePalmAimRay(ray),
|
||||
timestamp
|
||||
};
|
||||
}
|
||||
|
||||
function cloneTimedRay(ray: TimedPalmAimRay): TimedPalmAimRay {
|
||||
return {
|
||||
...clonePalmAimRay(ray),
|
||||
timestamp: ray.timestamp
|
||||
};
|
||||
}
|
||||
|
||||
function clonePalmAimRay(ray: PalmAimRay): PalmAimRay {
|
||||
return {
|
||||
direction: cloneVector(ray.direction),
|
||||
origin: cloneVector(ray.origin)
|
||||
};
|
||||
}
|
||||
|
||||
function cloneVector(vector: VectorLike): VectorLike {
|
||||
return {
|
||||
x: vector.x,
|
||||
y: vector.y,
|
||||
z: vector.z
|
||||
};
|
||||
}
|
||||
|
||||
function getFreshTimedRay(
|
||||
ray: TimedPalmAimRay | null,
|
||||
timestamp: number,
|
||||
maxAgeMs: number
|
||||
): TimedPalmAimRay | null {
|
||||
if (!ray) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const ageMs = Math.max(0, timestamp - ray.timestamp);
|
||||
return ageMs <= maxAgeMs ? ray : null;
|
||||
}
|
||||
|
||||
function averageVectors(vectors: Array<VectorLike | null | undefined>): VectorLike | null {
|
||||
const usableVectors = vectors.filter(Boolean) as VectorLike[];
|
||||
if (usableVectors.length === 0) {
|
||||
|
||||
Reference in New Issue
Block a user