import { AbstractMesh, DualShockPad, Engine, FreeCamera, FreeCameraGamepadInput, Gamepad as b_Gamepad, Mesh, Scene, Vector3, } from '@babylonjs/core/Legacy/legacy'; let buttonDown : { [button: number]: boolean } = {}; function latch(gp: Gamepad, button: number): boolean { let result = false; const b = gp.buttons[button]; if (b) { if (b.pressed) { if (!(buttonDown[button] ?? false)) result = true; buttonDown[button] = true; } else { buttonDown[button] = false; } } return result; } if ((navigator as any).oscpu?.startsWith('Linux')) { // ^ oscpu is undefined on chrome on Android, at least... DualShockPad.prototype.update = function () { b_Gamepad.prototype.update.call(this); (window as any).G = this; (this as any)._rightStickAxisX = 3; (this as any)._rightStickAxisY = 4; this.buttonCross = this.browserGamepad.buttons[0].value; this.buttonCircle = this.browserGamepad.buttons[1].value; this.buttonTriangle = this.browserGamepad.buttons[2].value; this.buttonSquare = this.browserGamepad.buttons[3].value; this.buttonL1 = this.browserGamepad.buttons[4].value; this.buttonR1 = this.browserGamepad.buttons[5].value; this.leftTrigger = this.browserGamepad.buttons[6].value; this.rightTrigger = this.browserGamepad.buttons[7].value; this.buttonShare = this.browserGamepad.buttons[8].value; this.buttonOptions = this.browserGamepad.buttons[9].value; this.buttonLeftStick = this.browserGamepad.buttons[11].value; this.buttonRightStick = this.browserGamepad.buttons[12].value; this.dPadUp = this.browserGamepad.axes[7].value < 0 ? 1 : 0; this.dPadDown = this.browserGamepad.axes[7].value > 0 ? 1 : 0; this.dPadLeft = this.browserGamepad.axes[6].value < 0 ? 1 : 0; this.dPadRight = this.browserGamepad.axes[6].value > 0 ? 1 : 0; }; } export type CreateScene = (canvas: HTMLCanvasElement, engine: Engine) => Promise<{ scene: Scene, floorMeshes: Mesh[] }>; export async function startEngine( createScene: CreateScene, initialPos = new Vector3(0, 1.6, -3.5), initialRotation = new Vector3(0, 0.5, 0).scaleInPlace(2 * Math.PI), ): Promise { const canvas = document.getElementById("renderCanvas") as HTMLCanvasElement; const engine = new Engine(canvas, true); const { scene, floorMeshes } = await createScene(canvas, engine); const xr = await scene.createDefaultXRExperienceAsync({}); const xrAvailable = xr?.baseExperience !== void 0; let camera: FreeCamera; if (xrAvailable) { camera = xr.baseExperience.camera; } else { camera = new FreeCamera("camera", initialPos, scene); camera.minZ = 0.1; camera.rotation = initialRotation; camera.attachControl(canvas, true); } const sm = xrAvailable ? xr.baseExperience.sessionManager : null; const gamepadInput = new FreeCameraGamepadInput(); gamepadInput.gamepadMoveSensibility = 320; gamepadInput.gamepadAngularSensibility = 100; camera.inputs.add(gamepadInput); gamepadInput.attachControl(); scene.gravity = new Vector3(0, -9.81 / 90, 0); scene.collisionsEnabled = true; camera.checkCollisions = true; camera.applyGravity = true; camera.ellipsoid = new Vector3(0.25, 0.8, 0.25); scene.getNodes().forEach(n => { if (n instanceof AbstractMesh) { n.checkCollisions = true; } }); const enableVR = () => { xr.baseExperience.enterXRAsync('immersive-vr', 'local').then(() => { xr.baseExperience.camera.position = initialPos; xr.baseExperience.camera.rotation = initialRotation; }); }; document.body.onclick = enableVR; let leanBase: { position: Vector3 } | null = null; engine.runRenderLoop(() => { for (const gp of Array.from(navigator.getGamepads())) { if (gp !== null) { if (sm) { const pos = new Vector3((gp.axes[0]), (-gp.axes[1]), (-gp.axes[3])); if (pos.length() > 0.0625) { if (leanBase === null) { leanBase = { position: xr.baseExperience.camera.position }; } xr.baseExperience.camera.position = pos.applyRotationQuaternion(xr.baseExperience.camera.absoluteRotation) .scale(0.25) .add(leanBase.position); } else { if (leanBase !== null) { xr.baseExperience.camera.position = leanBase.position; } leanBase = null; } } if (sm && latch(gp, 0)) { const ray = xr.baseExperience.camera.getForwardRay(); const hit = scene.pickWithRay(ray, m => floorMeshes.indexOf(m as any) !== -1); if (hit !== null) { if (floorMeshes.indexOf(hit.pickedMesh as any) !== -1) { if (hit.pickedPoint !== null) { xr.baseExperience.camera.position = hit.pickedPoint.add(new Vector3(0, 1.6, 0)); xr.baseExperience.camera.rotation = Vector3.Zero(); if (leanBase !== null) { leanBase.position = xr.baseExperience.camera.position; } } } } } if (latch(gp, 2)) { location.reload(); } if (sm && latch(gp, 3)) { enableVR(); } if (latch(gp, 5)) { const r = sm ? xr.baseExperience.camera.rotationQuaternion.toEulerAngles() : camera.rotation; r.y += Math.PI; r.y %= 2 * Math.PI; if (sm) { xr.baseExperience.camera.rotationQuaternion.copyFrom(r.toQuaternion()); } } if (sm && latch(gp, 4)) { xr.baseExperience.camera.rotationQuaternion.copyFrom(initialRotation.toQuaternion()); } } } scene.render(); }); window.addEventListener("resize", () => engine.resize()); }