Better XR support
This commit is contained in:
parent
24a7d2a55f
commit
d59de6f641
|
@ -79,17 +79,19 @@
|
|||
>
|
||||
>>>
|
||||
|
||||
<Exit "y" "lobby">
|
||||
<sprite "y"
|
||||
<move <v 12.0 0.75 -5.0>
|
||||
<texture ["textures/oak-herringbone-5e80fb40b00c9-1200.jpg"
|
||||
<v 1.0 1.0 1.0>
|
||||
<v 0.0 0.0 0.0>]
|
||||
<touchable
|
||||
<csg
|
||||
<intersect [
|
||||
<scale <v 0.5 1.0 0.5> <mesh <box>>>
|
||||
<mesh <sphere>>
|
||||
]>
|
||||
>>
|
||||
>>>
|
||||
; <Exit "y" "lobby">
|
||||
; <sprite "y"
|
||||
; <move <v 12.0 0.75 -5.0>
|
||||
; <texture ["textures/oak-herringbone-5e80fb40b00c9-1200.jpg"
|
||||
; <v 1.0 1.0 1.0>
|
||||
; <v 0.0 0.0 0.0>]
|
||||
; <touchable
|
||||
; <csg
|
||||
; <intersect [
|
||||
; <scale <v 0.5 1.0 0.5> <mesh <box>>>
|
||||
; <mesh <sphere>>
|
||||
; ]>
|
||||
; >>
|
||||
; >>>
|
||||
|
||||
[]
|
||||
|
|
217
src/engine.ts
217
src/engine.ts
|
@ -1,15 +1,19 @@
|
|||
import {
|
||||
AbstractMesh,
|
||||
DualShockPad,
|
||||
Engine,
|
||||
FreeCamera,
|
||||
FreeCameraGamepadInput,
|
||||
Gamepad as b_Gamepad,
|
||||
KeyboardEventTypes,
|
||||
Mesh,
|
||||
Quaternion,
|
||||
Scene,
|
||||
Vector3,
|
||||
WebXRCamera,
|
||||
WebXRDefaultExperience,
|
||||
WebXRSessionManager,
|
||||
WebXRState,
|
||||
} from '@babylonjs/core/Legacy/legacy';
|
||||
|
||||
import { log } from './log.js';
|
||||
|
@ -86,14 +90,20 @@ export class GamepadState {
|
|||
}
|
||||
|
||||
export class RunningEngine {
|
||||
camera!: FreeCamera;
|
||||
xrSessionManager: WebXRSessionManager | null = null;
|
||||
gamepadInput!: FreeCameraGamepadInput;
|
||||
// The active camera - plainCamera or xrCamera, depending.
|
||||
camera: FreeCamera;
|
||||
|
||||
plainCamera: FreeCamera;
|
||||
gamepadInput: FreeCameraGamepadInput;
|
||||
|
||||
xrCamera: WebXRCamera | null = null;
|
||||
|
||||
leanBase: { position: Vector3 } | null = null;
|
||||
recenterBase: { rotation: Quaternion } | null = null;
|
||||
|
||||
padStates: Map<number, GamepadState> = new Map();
|
||||
padStates = new Map<number, GamepadState>();
|
||||
keysDown = new Set<number>();
|
||||
keysChanged = new Map<number, boolean>();
|
||||
|
||||
static async start(
|
||||
interactivity: Interactivity,
|
||||
|
@ -106,8 +116,14 @@ export class RunningEngine {
|
|||
}, options0);
|
||||
const engine = new Engine(options.canvas, true);
|
||||
const scene = new Scene(engine);
|
||||
const xr = await scene.createDefaultXRExperienceAsync({});
|
||||
return new RunningEngine(options, engine, interactivity, scene, xr);
|
||||
return new RunningEngine(
|
||||
options,
|
||||
engine,
|
||||
interactivity,
|
||||
scene,
|
||||
(await WebXRSessionManager.IsSessionSupportedAsync('immersive-vr')
|
||||
? await scene.createDefaultXRExperienceAsync({})
|
||||
: null));
|
||||
}
|
||||
|
||||
private constructor (
|
||||
|
@ -115,41 +131,49 @@ export class RunningEngine {
|
|||
public engine: Engine,
|
||||
public interactivity: Interactivity,
|
||||
public scene: Scene,
|
||||
public xr: WebXRDefaultExperience,
|
||||
public xr: WebXRDefaultExperience | null,
|
||||
) {
|
||||
this.xrSessionManager = this.xr.baseExperience?.sessionManager ?? null;
|
||||
this.plainCamera = new FreeCamera("camera", this.options.initialPos.clone(), this.scene);
|
||||
this.plainCamera.rotation = this.options.initialRotation;
|
||||
this.plainCamera.minZ = 0.1;
|
||||
this.plainCamera.inertia = 0.75;
|
||||
this.plainCamera.speed = 0.5;
|
||||
this.plainCamera.keysUp.push(87 /* W */);
|
||||
this.plainCamera.keysLeft.push(65 /* A */);
|
||||
this.plainCamera.keysDown.push(83 /* S */);
|
||||
this.plainCamera.keysRight.push(68 /* D */);
|
||||
this.plainCamera.keysUpward.push(69 /* E */);
|
||||
this.plainCamera.keysDownward.push(81 /* Q */);
|
||||
this.plainCamera.attachControl(true);
|
||||
|
||||
if (this.xrSessionManager) {
|
||||
this.camera = this.xr.baseExperience.camera;
|
||||
} else {
|
||||
this.camera = new FreeCamera("camera",
|
||||
this.options.initialPos.clone(),
|
||||
this.scene);
|
||||
this.camera.rotation = this.options.initialRotation;
|
||||
this.camera.minZ = 0.1;
|
||||
this.camera.inertia = 0.75;
|
||||
this.camera.speed = 0.5;
|
||||
this.camera.attachControl(true);
|
||||
scene.onKeyboardObservable.add(info => {
|
||||
if (info.event.metaKey) return;
|
||||
this.keysChanged.set(info.event.keyCode, info.type === KeyboardEventTypes.KEYDOWN);
|
||||
});
|
||||
|
||||
this._setupCamera(this.plainCamera);
|
||||
|
||||
if (this.xr) {
|
||||
this.xr.baseExperience.onStateChangedObservable.add(state => this.xrStateChanged(state));
|
||||
this.xrCamera = this.xr.baseExperience.camera;
|
||||
this._setupCamera(this.xrCamera);
|
||||
}
|
||||
|
||||
this.camera = this.plainCamera;
|
||||
|
||||
this.gamepadInput = new FreeCameraGamepadInput();
|
||||
this.gamepadInput.gamepadMoveSensibility = 320;
|
||||
this.gamepadInput.gamepadAngularSensibility = 100;
|
||||
this.camera.inputs.add(this.gamepadInput);
|
||||
this.plainCamera.inputs.add(this.gamepadInput);
|
||||
this.gamepadInput.attachControl();
|
||||
|
||||
this.scene.gravity = new Vector3(0, -9.81 / 90, 0);
|
||||
this.scene.collisionsEnabled = true;
|
||||
|
||||
this.camera.checkCollisions = true;
|
||||
this.camera.applyGravity = false;
|
||||
(this.camera as any)._needMoveForGravity = true;
|
||||
this.camera.ellipsoid = new Vector3(0.25, this.options.initialPos.y / 2, 0.25);
|
||||
|
||||
if (this.options.canvas) {
|
||||
const canvas = this.options.canvas;
|
||||
if (this.xrSessionManager) {
|
||||
canvas.onclick = () => this.xrEnable();
|
||||
if (this.xrAvailable) {
|
||||
canvas.onclick = () => this.xrToggle();
|
||||
} else {
|
||||
canvas.onclick = () => canvas.requestPointerLock?.();
|
||||
}
|
||||
|
@ -160,6 +184,7 @@ export class RunningEngine {
|
|||
Array.from(navigator.getGamepads()).forEach(gp => {
|
||||
if (gp !== null) this.checkGamepadInput(gp);
|
||||
});
|
||||
this.checkKeys();
|
||||
this.scene.render();
|
||||
} catch (e) {
|
||||
console.error('Error in render loop', e);
|
||||
|
@ -170,6 +195,36 @@ export class RunningEngine {
|
|||
window.addEventListener("resize", () => this.engine.resize());
|
||||
}
|
||||
|
||||
_setupCamera(c: FreeCamera) {
|
||||
c.checkCollisions = true;
|
||||
c.applyGravity = false;
|
||||
(c as any)._needMoveForGravity = true;
|
||||
c.ellipsoid = new Vector3(0.25, this.options.initialPos.y / 2, 0.25);
|
||||
}
|
||||
|
||||
get inXR(): boolean {
|
||||
return (this.xr !== null) && (this.xr.baseExperience.state === WebXRState.IN_XR);
|
||||
}
|
||||
|
||||
get xrAvailable(): boolean {
|
||||
return this.xr !== null;
|
||||
}
|
||||
|
||||
set onCollide(c: (m: AbstractMesh) => void) {
|
||||
this.plainCamera.onCollide = c;
|
||||
if (this.xrCamera) this.xrCamera.onCollide = c;
|
||||
}
|
||||
|
||||
get position(): Vector3 {
|
||||
return this.camera.position;
|
||||
}
|
||||
|
||||
get rotation(): Vector3 {
|
||||
return this.inXR
|
||||
? this.camera.rotationQuaternion.toEulerAngles()
|
||||
: this.camera.rotation;
|
||||
}
|
||||
|
||||
padStateFor(gp: Gamepad): GamepadState {
|
||||
const state = this.padStates.get(gp.index);
|
||||
if (state) {
|
||||
|
@ -186,58 +241,82 @@ export class RunningEngine {
|
|||
checkGamepadInput(gp: Gamepad) {
|
||||
const state = this.padStateFor(gp);
|
||||
|
||||
if (state.latch(2)) location.reload();
|
||||
if (state.latch(5)) this.turn180();
|
||||
if (state.latch(9 /* options */)) location.reload();
|
||||
if (state.latch(3 /* triangle */)) this.turn180();
|
||||
|
||||
if (this.xrSessionManager) {
|
||||
this.updateLean(gp);
|
||||
if (state.latch(0)) this.xrTeleport();
|
||||
if (state.latch(1)) this.xrTouch();
|
||||
if (state.latch(3)) this.xrEnable();
|
||||
if (this.xrAvailable) {
|
||||
if (state.latch(16 /* ps */)) this.xrToggle();
|
||||
}
|
||||
|
||||
if (state.latch(4)) {
|
||||
if (this.inXR) {
|
||||
this.xrUpdateLean(gp);
|
||||
if (state.latch(0 /* cross */)) this.xrTeleport();
|
||||
if (state.latch(1 /* circle */)) this.xrTouch();
|
||||
|
||||
if (state.latch(8 /* share */)) {
|
||||
this.recenterBase = {
|
||||
rotation: this.xr.baseExperience.camera.rotationQuaternion.clone(),
|
||||
rotation: this.xrCamera!.rotationQuaternion.clone(),
|
||||
};
|
||||
}
|
||||
if (state.isDown(4) && this.recenterBase) {
|
||||
this.xr.baseExperience.camera.rotationQuaternion.copyFrom(
|
||||
if (state.isDown(8 /* share */) && this.recenterBase) {
|
||||
this.xrCamera!.rotationQuaternion.copyFrom(
|
||||
this.recenterBase.rotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateLean(gp: Gamepad) {
|
||||
checkKeys() {
|
||||
for (const [keyCode, state] of this.keysChanged.entries()) {
|
||||
if (state) {
|
||||
this.keysDown.add(keyCode);
|
||||
switch (keyCode) {
|
||||
case 32 /* space */: this.jump(); break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
this.keysDown.delete(keyCode);
|
||||
}
|
||||
}
|
||||
this.keysChanged.clear();
|
||||
}
|
||||
|
||||
jump() {
|
||||
if (Math.abs(this.camera.cameraDirection.y) < 0.1) {
|
||||
this.camera.cameraDirection.y += 1;
|
||||
}
|
||||
}
|
||||
|
||||
xrUpdateLean(gp: Gamepad) {
|
||||
const pos = new Vector3((gp.axes[0]), (-gp.axes[1]), (-gp.axes[3]));
|
||||
if (pos.length() > 0.0625) {
|
||||
if (this.leanBase === null) {
|
||||
this.leanBase = { position: this.xr.baseExperience.camera.position };
|
||||
this.leanBase = { position: this.xrCamera!.position };
|
||||
}
|
||||
this.xr.baseExperience.camera.position =
|
||||
pos.applyRotationQuaternion(this.xr.baseExperience.camera.absoluteRotation)
|
||||
this.xrCamera!.position =
|
||||
pos.applyRotationQuaternion(this.xrCamera!.absoluteRotation)
|
||||
.scale(0.25)
|
||||
.add(this.leanBase.position);
|
||||
} else {
|
||||
if (this.leanBase !== null) {
|
||||
this.xr.baseExperience.camera.position = this.leanBase.position;
|
||||
this.xrCamera!.position = this.leanBase.position;
|
||||
}
|
||||
this.leanBase = null;
|
||||
}
|
||||
}
|
||||
|
||||
turn180() {
|
||||
const r = this.xrSessionManager
|
||||
? this.xr.baseExperience.camera.rotationQuaternion.toEulerAngles()
|
||||
const r = this.inXR
|
||||
? this.xrCamera!.rotationQuaternion.toEulerAngles()
|
||||
: this.camera.rotation;
|
||||
r.y += Math.PI;
|
||||
r.y %= 2 * Math.PI;
|
||||
if (this.xrSessionManager) {
|
||||
this.xr.baseExperience.camera.rotationQuaternion.copyFrom(r.toQuaternion());
|
||||
if (this.inXR) {
|
||||
this.xrCamera!.rotationQuaternion.copyFrom(r.toQuaternion());
|
||||
}
|
||||
}
|
||||
|
||||
xrTouch() {
|
||||
const ray = this.xr.baseExperience.camera.getForwardRay();
|
||||
const ray = this.xrCamera!.getForwardRay();
|
||||
const meshes = this.interactivity.touchableMeshes();
|
||||
const hit = this.scene.pickWithRay(ray, m => meshes.indexOf(m as any) !== -1);
|
||||
|
||||
|
@ -248,9 +327,9 @@ export class RunningEngine {
|
|||
}
|
||||
|
||||
xrTeleport() {
|
||||
if (!this.xrSessionManager) return;
|
||||
if (!this.inXR) return;
|
||||
|
||||
const ray = this.xr.baseExperience.camera.getForwardRay();
|
||||
const ray = this.xrCamera!.getForwardRay();
|
||||
const meshes = this.interactivity.floorMeshes();
|
||||
const hit = this.scene.pickWithRay(ray, m => meshes.indexOf(m as any) !== -1);
|
||||
|
||||
|
@ -259,17 +338,37 @@ export class RunningEngine {
|
|||
if (hit.pickedPoint === null) return;
|
||||
|
||||
const pos = hit.pickedPoint.add(new Vector3(0, 1.6, 0));
|
||||
this.xr.baseExperience.camera.position = pos;
|
||||
this.xr.baseExperience.camera.rotation = Vector3.Zero();
|
||||
this.xrCamera!.position = pos;
|
||||
this.xrCamera!.rotation = Vector3.Zero();
|
||||
if (this.leanBase !== null) this.leanBase.position = pos;
|
||||
}
|
||||
|
||||
xrEnable() {
|
||||
if (!this.xrSessionManager) return;
|
||||
this.xr.baseExperience.enterXRAsync('immersive-vr', 'local').then(() => {
|
||||
this.xr.baseExperience.camera.position = this.options.initialPos.clone();
|
||||
this.xr.baseExperience.camera.rotation = this.options.initialRotation;
|
||||
this.xr.baseExperience.sessionManager.session.onselect = () => this.xrTeleport();
|
||||
});
|
||||
xrStateChanged(state: WebXRState) {
|
||||
switch (state) {
|
||||
case WebXRState.IN_XR:
|
||||
break;
|
||||
case WebXRState.ENTERING_XR:
|
||||
this.camera = this.xrCamera!;
|
||||
this.camera.position = this.plainCamera.position.clone();
|
||||
this.camera.rotationQuaternion = this.options.initialRotation.toQuaternion();
|
||||
break;
|
||||
case WebXRState.EXITING_XR:
|
||||
this.camera = this.plainCamera;
|
||||
this.camera.position = this.xrCamera!.position.clone();
|
||||
this.camera.rotation = this.xrCamera!.rotationQuaternion.toEulerAngles();
|
||||
break;
|
||||
case WebXRState.NOT_IN_XR:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
xrToggle() {
|
||||
if (this.inXR) {
|
||||
this.xr!.baseExperience.exitXRAsync();
|
||||
} else {
|
||||
this.xr!.baseExperience.enterXRAsync('immersive-vr', 'local').then(() => {
|
||||
this.xr!.baseExperience.sessionManager.session.onselect = () => this.xrTeleport();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
16
src/index.ts
16
src/index.ts
|
@ -70,15 +70,14 @@ async function enterScene(
|
|||
|
||||
const rootMesh = new Mesh('--root-' + (+new Date()), runningEngine.scene);
|
||||
|
||||
const camera = runningEngine.camera;
|
||||
camera.applyGravity = false;
|
||||
camera.position = initialPosition.clone();
|
||||
runningEngine.camera.applyGravity = false;
|
||||
runningEngine.camera.position = initialPosition.clone();
|
||||
|
||||
interpretScene(id, runningEngine, rootMesh, sceneDs);
|
||||
|
||||
let lastTouchTime = 0;
|
||||
let lastTouchSpriteName = "";
|
||||
camera.onCollide = (other: AbstractMesh) => {
|
||||
runningEngine.onCollide = (other: AbstractMesh) => {
|
||||
if (other.metadata?.touchable) {
|
||||
const now = +new Date();
|
||||
const touched = other.metadata?.spriteName ?? "";
|
||||
|
@ -97,11 +96,8 @@ async function enterScene(
|
|||
}
|
||||
};
|
||||
|
||||
const currentPosition = () => Shapes.Vector3(camera.position);
|
||||
const currentRotation = () => Shapes.Vector3(
|
||||
camera instanceof WebXRCamera
|
||||
? camera.rotationQuaternion.toEulerAngles()
|
||||
: camera.rotation);
|
||||
const currentPosition = () => Shapes.Vector3(runningEngine.position);
|
||||
const currentRotation = () => Shapes.Vector3(runningEngine.rotation);
|
||||
|
||||
field position: Shapes.Vector3 = currentPosition();
|
||||
field rotation: Shapes.Vector3 = currentRotation();
|
||||
|
@ -137,7 +133,7 @@ async function enterScene(
|
|||
switch (dest._variant) {
|
||||
case "local":
|
||||
if (dest.value === sceneDs) {
|
||||
camera.position = newPos;
|
||||
runningEngine.camera.position = newPos;
|
||||
} else {
|
||||
runningEngine.scene.removeMesh(rootMesh, true);
|
||||
Turn.active.stop(currentSceneFacet, () => {
|
||||
|
|
Loading…
Reference in New Issue