191 lines
6.9 KiB
TypeScript
191 lines
6.9 KiB
TypeScript
import {
|
|
DualShockPad,
|
|
Engine,
|
|
FreeCamera,
|
|
FreeCameraGamepadInput,
|
|
Gamepad as b_Gamepad,
|
|
Mesh,
|
|
Quaternion,
|
|
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<CreatedScene>;
|
|
export type CreatedScene = {
|
|
scene: Scene,
|
|
floorMeshes: () => Mesh[]
|
|
};
|
|
|
|
export async function startEngine(
|
|
createScene: CreateScene,
|
|
initialPos = new Vector3(0, 1.6, 0),
|
|
initialRotation = new Vector3(0, 0, 0).scaleInPlace(2 * Math.PI),
|
|
): Promise<Scene> {
|
|
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.speed = 0.5;
|
|
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);
|
|
|
|
const enableVR = () => {
|
|
if (xrAvailable) {
|
|
xr.baseExperience.enterXRAsync('immersive-vr', 'local').then(() => {
|
|
xr.baseExperience.camera.position = initialPos;
|
|
xr.baseExperience.camera.rotation = initialRotation;
|
|
});
|
|
}
|
|
};
|
|
|
|
canvas.onclick = enableVR;
|
|
|
|
let leanBase: { position: Vector3 } | null = null;
|
|
let recenterBase: { rotation: Quaternion } | 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 meshes = floorMeshes();
|
|
const hit = scene.pickWithRay(ray, m => meshes.indexOf(m as any) !== -1);
|
|
if (hit !== null) {
|
|
if (meshes.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) {
|
|
if (latch(gp, 4)) {
|
|
recenterBase = { rotation: xr.baseExperience.camera.rotationQuaternion.clone() };
|
|
}
|
|
if (buttonDown[4] && recenterBase) {
|
|
xr.baseExperience.camera.rotationQuaternion.copyFrom(recenterBase.rotation);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
scene.render();
|
|
});
|
|
window.addEventListener("resize", () => engine.resize());
|
|
|
|
return scene;
|
|
}
|