Files
webeditor3d/src/runtime-three/player-input-bindings.ts

351 lines
9.9 KiB
TypeScript

import type {
PlayerStartGamepadActionBinding,
PlayerStartGamepadCameraLookBinding,
PlayerStartGamepadBinding,
PlayerStartInputBindings
} from "../entities/entity-instances";
const GAMEPAD_AXIS_DEADZONE = 0.18;
export interface PlayerStartMovementActionState {
moveForward: number;
moveBackward: number;
moveLeft: number;
moveRight: number;
}
export interface PlayerStartActionInputState
extends PlayerStartMovementActionState {
jump: number;
sprint: number;
crouch: number;
interact: number;
clearTarget: number;
pauseTime: number;
}
export interface PlayerStartLookInputState {
horizontal: number;
vertical: number;
}
function clampUnitInterval(value: number): number {
return Math.max(0, Math.min(1, value));
}
function readGamepadButtonStrength(button: GamepadButton | undefined): number {
if (button === undefined) {
return 0;
}
return clampUnitInterval(button.pressed ? Math.max(button.value, 1) : button.value);
}
function readCenteredAxisValue(value: number | undefined): number {
if (value === undefined || !Number.isFinite(value)) {
return 0;
}
const magnitude = Math.abs(value);
if (magnitude <= GAMEPAD_AXIS_DEADZONE) {
return 0;
}
const normalizedMagnitude = clampUnitInterval(
(magnitude - GAMEPAD_AXIS_DEADZONE) / (1 - GAMEPAD_AXIS_DEADZONE)
);
return Math.sign(value) * normalizedMagnitude;
}
function readPositiveAxisStrength(value: number | undefined): number {
if (value === undefined || !Number.isFinite(value) || value <= 0) {
return 0;
}
if (value <= GAMEPAD_AXIS_DEADZONE) {
return 0;
}
return clampUnitInterval(
(value - GAMEPAD_AXIS_DEADZONE) / (1 - GAMEPAD_AXIS_DEADZONE)
);
}
function readNegativeAxisStrength(value: number | undefined): number {
return readPositiveAxisStrength(
value === undefined || !Number.isFinite(value) ? value : -value
);
}
function readSingleGamepadBinding(
gamepad: Gamepad,
binding: PlayerStartGamepadBinding
): number {
switch (binding) {
case "leftStickUp":
return readNegativeAxisStrength(gamepad.axes[1]);
case "leftStickDown":
return readPositiveAxisStrength(gamepad.axes[1]);
case "leftStickLeft":
return readNegativeAxisStrength(gamepad.axes[0]);
case "leftStickRight":
return readPositiveAxisStrength(gamepad.axes[0]);
case "dpadUp":
return readGamepadButtonStrength(gamepad.buttons[12]);
case "dpadDown":
return readGamepadButtonStrength(gamepad.buttons[13]);
case "dpadLeft":
return readGamepadButtonStrength(gamepad.buttons[14]);
case "dpadRight":
return readGamepadButtonStrength(gamepad.buttons[15]);
}
}
function readGamepadBindingStrength(
gamepads: ArrayLike<Gamepad | null> | null | undefined,
binding: PlayerStartGamepadBinding
): number {
if (gamepads === undefined || gamepads === null) {
return 0;
}
let strength = 0;
for (let index = 0; index < gamepads.length; index += 1) {
const gamepad = gamepads[index];
if (gamepad === null || gamepad === undefined || gamepad.connected === false) {
continue;
}
strength = Math.max(strength, readSingleGamepadBinding(gamepad, binding));
}
return strength;
}
function readSingleGamepadActionBinding(
gamepad: Gamepad,
binding: PlayerStartGamepadActionBinding
): number {
switch (binding) {
case "buttonSouth":
return readGamepadButtonStrength(gamepad.buttons[0]);
case "buttonEast":
return readGamepadButtonStrength(gamepad.buttons[1]);
case "buttonWest":
return readGamepadButtonStrength(gamepad.buttons[2]);
case "buttonNorth":
return readGamepadButtonStrength(gamepad.buttons[3]);
case "buttonMenu":
return readGamepadButtonStrength(gamepad.buttons[9]);
case "leftShoulder":
return readGamepadButtonStrength(gamepad.buttons[4]);
case "rightShoulder":
return readGamepadButtonStrength(gamepad.buttons[5]);
case "leftTrigger":
return readGamepadButtonStrength(gamepad.buttons[6]);
case "rightTrigger":
return readGamepadButtonStrength(gamepad.buttons[7]);
case "leftStickPress":
return readGamepadButtonStrength(gamepad.buttons[10]);
case "rightStickPress":
return readGamepadButtonStrength(gamepad.buttons[11]);
}
}
function readGamepadActionBindingStrength(
gamepads: ArrayLike<Gamepad | null> | null | undefined,
binding: PlayerStartGamepadActionBinding
): number {
if (gamepads === undefined || gamepads === null) {
return 0;
}
let strength = 0;
for (let index = 0; index < gamepads.length; index += 1) {
const gamepad = gamepads[index];
if (gamepad === null || gamepad === undefined || gamepad.connected === false) {
continue;
}
strength = Math.max(strength, readSingleGamepadActionBinding(gamepad, binding));
}
return strength;
}
function readSingleGamepadCameraLook(
gamepad: Gamepad,
binding: PlayerStartGamepadCameraLookBinding
): PlayerStartLookInputState {
switch (binding) {
case "rightStick":
return {
horizontal: readCenteredAxisValue(gamepad.axes[2]),
vertical: -readCenteredAxisValue(gamepad.axes[3])
};
}
}
function readGamepadCameraLook(
gamepads: ArrayLike<Gamepad | null> | null | undefined,
binding: PlayerStartGamepadCameraLookBinding
): PlayerStartLookInputState {
if (gamepads === undefined || gamepads === null) {
return {
horizontal: 0,
vertical: 0
};
}
let horizontal = 0;
let vertical = 0;
for (let index = 0; index < gamepads.length; index += 1) {
const gamepad = gamepads[index];
if (gamepad === null || gamepad === undefined || gamepad.connected === false) {
continue;
}
const look = readSingleGamepadCameraLook(gamepad, binding);
if (Math.abs(look.horizontal) > Math.abs(horizontal)) {
horizontal = look.horizontal;
}
if (Math.abs(look.vertical) > Math.abs(vertical)) {
vertical = look.vertical;
}
}
return {
horizontal,
vertical
};
}
export function getAvailableGamepads(): ArrayLike<Gamepad | null> | undefined {
if (
typeof navigator === "undefined" ||
typeof navigator.getGamepads !== "function"
) {
return undefined;
}
return navigator.getGamepads();
}
export function resolvePlayerStartMovementActions(
pressedKeys: ReadonlySet<string>,
bindings: PlayerStartInputBindings,
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
): PlayerStartMovementActionState {
const actionInputs = resolvePlayerStartActionInputs(
pressedKeys,
bindings,
gamepads
);
return {
moveForward: actionInputs.moveForward,
moveBackward: actionInputs.moveBackward,
moveLeft: actionInputs.moveLeft,
moveRight: actionInputs.moveRight
};
}
export function resolvePlayerStartActionInputs(
pressedKeys: ReadonlySet<string>,
bindings: PlayerStartInputBindings,
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
): PlayerStartActionInputState {
return {
moveForward: Math.max(
pressedKeys.has(bindings.keyboard.moveForward) ? 1 : 0,
readGamepadBindingStrength(gamepads, bindings.gamepad.moveForward)
),
moveBackward: Math.max(
pressedKeys.has(bindings.keyboard.moveBackward) ? 1 : 0,
readGamepadBindingStrength(gamepads, bindings.gamepad.moveBackward)
),
moveLeft: Math.max(
pressedKeys.has(bindings.keyboard.moveLeft) ? 1 : 0,
readGamepadBindingStrength(gamepads, bindings.gamepad.moveLeft)
),
moveRight: Math.max(
pressedKeys.has(bindings.keyboard.moveRight) ? 1 : 0,
readGamepadBindingStrength(gamepads, bindings.gamepad.moveRight)
),
jump: Math.max(
pressedKeys.has(bindings.keyboard.jump) ? 1 : 0,
readGamepadActionBindingStrength(gamepads, bindings.gamepad.jump)
),
sprint: Math.max(
pressedKeys.has(bindings.keyboard.sprint) ? 1 : 0,
readGamepadActionBindingStrength(gamepads, bindings.gamepad.sprint)
),
crouch: Math.max(
pressedKeys.has(bindings.keyboard.crouch) ? 1 : 0,
readGamepadActionBindingStrength(gamepads, bindings.gamepad.crouch)
),
interact: Math.max(
pressedKeys.has(bindings.keyboard.interact) ? 1 : 0,
readGamepadActionBindingStrength(gamepads, bindings.gamepad.interact)
),
clearTarget: Math.max(
pressedKeys.has(bindings.keyboard.clearTarget) ? 1 : 0,
readGamepadActionBindingStrength(gamepads, bindings.gamepad.clearTarget)
),
pauseTime: Math.max(
pressedKeys.has(bindings.keyboard.pauseTime) ? 1 : 0,
readGamepadActionBindingStrength(gamepads, bindings.gamepad.pauseTime)
)
};
}
export function resolvePlayerStartPauseInput(
pressedKeys: ReadonlySet<string>,
bindings: PlayerStartInputBindings,
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
): number {
return resolvePlayerStartActionInputs(pressedKeys, bindings, gamepads)
.pauseTime;
}
export function resolvePlayerStartInteractInput(
pressedKeys: ReadonlySet<string>,
bindings: PlayerStartInputBindings,
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
): number {
return resolvePlayerStartActionInputs(pressedKeys, bindings, gamepads)
.interact;
}
export function resolvePlayerStartClearTargetInput(
pressedKeys: ReadonlySet<string>,
bindings: PlayerStartInputBindings,
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
): number {
return resolvePlayerStartActionInputs(pressedKeys, bindings, gamepads)
.clearTarget;
}
export function resolveDefaultTargetCycleInput(
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
): number {
return readGamepadActionBindingStrength(gamepads, "rightStickPress");
}
export function resolvePlayerStartLookInput(
bindings: PlayerStartInputBindings,
gamepads: ArrayLike<Gamepad | null> | null | undefined = getAvailableGamepads()
): PlayerStartLookInputState {
return readGamepadCameraLook(gamepads, bindings.gamepad.cameraLook);
}