forked from EXT/VR180-Web-Player
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
import {
|
||||
DEFAULT_HEAD_LOCK,
|
||||
DEFAULT_PROJECTION,
|
||||
PLAYER_SELECTOR,
|
||||
type HeadLockMode,
|
||||
type ProjectionMode,
|
||||
VALID_HEAD_LOCKS,
|
||||
VALID_PROJECTIONS
|
||||
} from './config.js';
|
||||
import { create2DControlPanel, createPlayButton, injectPlayerStyles } from './dom/dom.js';
|
||||
import { createMediaAdapter, type SupportedMediaAdapter } from './media/media-adapter.js';
|
||||
|
||||
export type BootstrapContext = {
|
||||
headLockMode: HeadLockMode;
|
||||
mediaAdapter: SupportedMediaAdapter;
|
||||
playButton: HTMLButtonElement;
|
||||
playerContainer: HTMLElement;
|
||||
@@ -39,6 +43,12 @@ export function bootstrapPlayer(playerBase: string, onReady: (context: Bootstrap
|
||||
return;
|
||||
}
|
||||
|
||||
const configuredHeadLock = (playerContainer.dataset.headLock || DEFAULT_HEAD_LOCK).trim().toLowerCase();
|
||||
if (!VALID_HEAD_LOCKS.has(configuredHeadLock as HeadLockMode)) {
|
||||
console.error(`VR_WEB_PLAYER_CONFIG: Unsupported data-head-lock="${configuredHeadLock}". Use "auto", "position", or "none".`);
|
||||
return;
|
||||
}
|
||||
|
||||
const mediaAdapter = createMediaAdapter(playerContainer);
|
||||
if (!mediaAdapter) {
|
||||
console.error(`VR_WEB_PLAYER_DOM: ${PLAYER_SELECTOR} must contain exactly one supported media element: video or img.`);
|
||||
@@ -62,6 +72,7 @@ export function bootstrapPlayer(playerBase: string, onReady: (context: Bootstrap
|
||||
|
||||
completeXrSupportCheck(playButton, () => {
|
||||
onReady({
|
||||
headLockMode: configuredHeadLock as HeadLockMode,
|
||||
mediaAdapter,
|
||||
playButton,
|
||||
playerContainer,
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
export const PLAYER_SELECTOR = '[data-vr-web-player]';
|
||||
|
||||
export type ProjectionMode = 'vr180' | 'plane';
|
||||
export type HeadLockMode = 'auto' | 'position' | 'none';
|
||||
|
||||
export const DEFAULT_PROJECTION: ProjectionMode = 'vr180';
|
||||
export const DEFAULT_HEAD_LOCK: HeadLockMode = 'auto';
|
||||
export const VALID_PROJECTIONS = new Set<ProjectionMode>(['vr180', 'plane']);
|
||||
export const VALID_HEAD_LOCKS = new Set<HeadLockMode>(['auto', 'position', 'none']);
|
||||
|
||||
export const PLANE_WIDTH = 3.2;
|
||||
export const PLANE_HEIGHT = PLANE_WIDTH * (9 / 16);
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
import {
|
||||
PLANE_DISTANCE,
|
||||
type HeadLockMode,
|
||||
type ProjectionMode
|
||||
} from '../config.js';
|
||||
|
||||
export function isLeftEyeCamera(renderingRenderer: any, activeCamera: any): boolean {
|
||||
const xrCamera = renderingRenderer.xr.getCamera();
|
||||
|
||||
@@ -78,3 +84,70 @@ export function positionPlaneForPresentation(
|
||||
const zPosition = isFallback2D && camera2D ? camera2D.position.z - plane2DDistance : -planeDistance;
|
||||
planeMesh.position.set(0, 1.6, zPosition);
|
||||
}
|
||||
|
||||
export function shouldLockContentToHeadPosition(headLockMode: HeadLockMode, projectionMode: ProjectionMode): boolean {
|
||||
if (headLockMode === 'position') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (headLockMode === 'none') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return projectionMode === 'vr180';
|
||||
}
|
||||
|
||||
export function applyHeadPositionLock(
|
||||
contentMesh: any,
|
||||
activeCamera: any,
|
||||
projectionMode: ProjectionMode,
|
||||
isHeadPositionLocked: boolean,
|
||||
planeDistance = PLANE_DISTANCE
|
||||
): void {
|
||||
if (!contentMesh || !activeCamera || !isHeadPositionLocked) {
|
||||
return;
|
||||
}
|
||||
|
||||
const cameraPosition = getCameraWorldPosition(activeCamera);
|
||||
if (!cameraPosition) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (projectionMode === 'plane') {
|
||||
contentMesh.position.set(cameraPosition.x, cameraPosition.y, cameraPosition.z - planeDistance);
|
||||
return;
|
||||
}
|
||||
|
||||
contentMesh.position.set(cameraPosition.x, cameraPosition.y, cameraPosition.z);
|
||||
}
|
||||
|
||||
export function resetHeadPositionLockedContent(
|
||||
vr180Mesh: any,
|
||||
planeMesh: any,
|
||||
planeDistance = PLANE_DISTANCE
|
||||
): void {
|
||||
vr180Mesh?.position?.set?.(0, 0, 0);
|
||||
planeMesh?.position?.set?.(0, 1.6, -planeDistance);
|
||||
}
|
||||
|
||||
function getCameraWorldPosition(activeCamera: any): { x: number; y: number; z: number } | null {
|
||||
const matrixElements = activeCamera?.matrixWorld?.elements;
|
||||
if (matrixElements && matrixElements.length >= 16) {
|
||||
return {
|
||||
x: matrixElements[12],
|
||||
y: matrixElements[13],
|
||||
z: matrixElements[14]
|
||||
};
|
||||
}
|
||||
|
||||
const position = activeCamera?.position;
|
||||
if (position) {
|
||||
return {
|
||||
x: position.x || 0,
|
||||
y: position.y || 0,
|
||||
z: position.z || 0
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
import {
|
||||
PLANE_2D_DISTANCE,
|
||||
PLANE_DISTANCE,
|
||||
type HeadLockMode,
|
||||
type ProjectionMode
|
||||
} from './config.js';
|
||||
import { bootstrapPlayer } from './bootstrap.js';
|
||||
import { createContentScene } from './rendering/content-scene.js';
|
||||
import {
|
||||
applyHeadPositionLock as applyHeadPositionLockCore,
|
||||
applySbsTextureWindow as applySbsTextureWindowCore,
|
||||
hideContentMeshes as hideContentMeshesCore,
|
||||
positionPlaneForPresentation as positionPlaneForPresentationCore,
|
||||
resetHeadPositionLockedContent as resetHeadPositionLockedContentCore,
|
||||
shouldLockContentToHeadPosition,
|
||||
showActiveContentMesh as showActiveContentMeshCore
|
||||
} from './rendering/projection.js';
|
||||
import { createMediaTexture as createMediaTextureCore } from './rendering/three-utils.js';
|
||||
@@ -37,7 +41,7 @@ import type { SupportedMediaAdapter } from './media/media-adapter.js';
|
||||
|
||||
const _playerBase = new URL('.', import.meta.url).href;
|
||||
|
||||
let playerContainer, projectionMode: ProjectionMode;
|
||||
let playerContainer, projectionMode: ProjectionMode, headLockMode: HeadLockMode;
|
||||
let scene, camera, renderer, video, sphereMaterial;
|
||||
let vr180Mesh, planeMesh, activeContentMesh;
|
||||
let xrSession = null;
|
||||
@@ -62,6 +66,7 @@ let fallbackCameraControls: FallbackCameraControls | undefined;
|
||||
bootstrapPlayer(_playerBase, (context) => {
|
||||
playerContainer = context.playerContainer;
|
||||
projectionMode = context.projectionMode;
|
||||
headLockMode = context.headLockMode;
|
||||
mediaAdapter = context.mediaAdapter;
|
||||
video = mediaAdapter.kind === 'video' ? mediaAdapter.element : undefined;
|
||||
playBtn = context.playButton;
|
||||
@@ -85,6 +90,25 @@ function positionPlaneForPresentation(isFallback2D = false) {
|
||||
positionPlaneForPresentationCore(planeMesh, camera2D, isFallback2D, PLANE_DISTANCE, PLANE_2D_DISTANCE);
|
||||
}
|
||||
|
||||
function updateHeadPositionLock() {
|
||||
if (!renderer?.xr?.isPresenting || !activeContentMesh) {
|
||||
return;
|
||||
}
|
||||
|
||||
const xrCamera = renderer.xr.getCamera?.(camera) || camera;
|
||||
applyHeadPositionLockCore(
|
||||
activeContentMesh,
|
||||
xrCamera,
|
||||
projectionMode,
|
||||
shouldLockContentToHeadPosition(headLockMode, projectionMode),
|
||||
PLANE_DISTANCE
|
||||
);
|
||||
}
|
||||
|
||||
function resetHeadPositionLock() {
|
||||
resetHeadPositionLockedContentCore(vr180Mesh, planeMesh, PLANE_DISTANCE);
|
||||
}
|
||||
|
||||
function createMediaTexture() {
|
||||
if (!textureManager) {
|
||||
throw new Error('Media texture manager is not initialized.');
|
||||
@@ -403,6 +427,7 @@ async function handleEnterVRButtonClick() {
|
||||
await actualSessionToggle();
|
||||
} else {
|
||||
// VR is not supported - start 2D rectilinear mode
|
||||
resetHeadPositionLock();
|
||||
twoDMode?.start();
|
||||
}
|
||||
}
|
||||
@@ -526,6 +551,7 @@ function onVRSessionEnd(event) {
|
||||
|
||||
textureManager?.clearMaterial(sphereMaterial);
|
||||
hideContentMeshes();
|
||||
resetHeadPositionLock();
|
||||
if (vrControlPanel) {
|
||||
vrPanelVisibility.hideImmediately();
|
||||
}
|
||||
@@ -585,6 +611,7 @@ function renderXR(timestamp, frame) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
updateHeadPositionLock();
|
||||
textureManager?.updateIfNeeded();
|
||||
renderer.render(scene, camera);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user