@pmndrs/viverse 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/action/action.d.ts +40 -0
- package/dist/action/action.js +114 -0
- package/dist/action/index.d.ts +18 -0
- package/dist/action/index.js +25 -0
- package/dist/action/keyboard.d.ts +26 -0
- package/dist/action/keyboard.js +89 -0
- package/dist/action/pointer-capture.d.ts +8 -0
- package/dist/{input → action}/pointer-capture.js +17 -42
- package/dist/action/pointer-lock.d.ts +6 -0
- package/dist/action/pointer-lock.js +49 -0
- package/dist/action/pointer.d.ts +18 -0
- package/dist/action/pointer.js +90 -0
- package/dist/action/screen-joystick.d.ts +16 -0
- package/dist/action/screen-joystick.js +120 -0
- package/dist/action/screen-jump-button.d.ts +6 -0
- package/dist/action/screen-jump-button.js +32 -0
- package/dist/animation/bone-map.d.ts +4 -0
- package/dist/animation/bone-map.js +11 -0
- package/dist/animation/default.d.ts +9 -1
- package/dist/animation/default.js +16 -9
- package/dist/animation/index.d.ts +8 -13
- package/dist/animation/index.js +61 -39
- package/dist/animation/mask.d.ts +4 -1
- package/dist/animation/mask.js +50 -0
- package/dist/camera.d.ts +8 -4
- package/dist/camera.js +24 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/model/index.d.ts +1 -2
- package/dist/simple-character/apply-input-options.d.ts +2 -0
- package/dist/simple-character/apply-input-options.js +34 -0
- package/dist/simple-character/index.d.ts +33 -10
- package/dist/simple-character/index.js +18 -19
- package/dist/simple-character/state/jump-down.js +2 -1
- package/dist/simple-character/state/jump-forward.js +4 -2
- package/dist/simple-character/state/jump-loop.js +2 -1
- package/dist/simple-character/state/jump-start.js +2 -2
- package/dist/simple-character/state/jump-up.js +4 -2
- package/dist/simple-character/state/movement.js +12 -8
- package/dist/simple-character/update-input-velocity.d.ts +1 -2
- package/dist/simple-character/update-input-velocity.js +4 -4
- package/dist/simple-character/update-rotation.js +1 -1
- package/dist/utils.d.ts +5 -5
- package/dist/utils.js +15 -6
- package/package.json +2 -2
- package/dist/animation/bvh.d.ts +0 -4
- package/dist/animation/bvh.js +0 -8
- package/dist/animation/fbx.d.ts +0 -4
- package/dist/animation/fbx.js +0 -8
- package/dist/animation/gltf.d.ts +0 -3
- package/dist/animation/gltf.js +0 -8
- package/dist/animation/vrma.d.ts +0 -3
- package/dist/animation/vrma.js +0 -8
- package/dist/input/index.d.ts +0 -31
- package/dist/input/index.js +0 -77
- package/dist/input/keyboard.d.ts +0 -16
- package/dist/input/keyboard.js +0 -82
- package/dist/input/pointer-capture.d.ts +0 -20
- package/dist/input/pointer-lock.d.ts +0 -17
- package/dist/input/pointer-lock.js +0 -55
- package/dist/input/screen-joystick.d.ts +0 -18
- package/dist/input/screen-joystick.js +0 -120
- package/dist/input/screen-jump-button.d.ts +0 -8
- package/dist/input/screen-jump-button.js +0 -49
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export type WriteonlyEventAction<T = void> = {
|
|
2
|
+
emit(value: T): void;
|
|
3
|
+
};
|
|
4
|
+
export declare class EventAction<T = unknown> implements WriteonlyEventAction<T> {
|
|
5
|
+
private readonly combine?;
|
|
6
|
+
private readonly neutral?;
|
|
7
|
+
private latestTime;
|
|
8
|
+
private readonly subscriptionListeners;
|
|
9
|
+
private readonly readers;
|
|
10
|
+
constructor(combine?: ((...values: Array<T>) => T) | undefined, neutral?: T | undefined);
|
|
11
|
+
emit(value: T): void;
|
|
12
|
+
subscribe(callback: (value: T) => void, options?: {
|
|
13
|
+
once?: true;
|
|
14
|
+
signal?: AbortSignal;
|
|
15
|
+
}): void;
|
|
16
|
+
waitFor(signal?: AbortSignal): Promise<T>;
|
|
17
|
+
getLatestTime(): number;
|
|
18
|
+
createReader(abortSignal: AbortSignal): {
|
|
19
|
+
update(): void;
|
|
20
|
+
get(): T;
|
|
21
|
+
};
|
|
22
|
+
mapFrom<S>(fn: (value: S) => T, abortSignal?: AbortSignal): EventAction<S>;
|
|
23
|
+
filterFrom(fn: (value: T) => boolean, abortSignal?: AbortSignal): EventAction<T>;
|
|
24
|
+
}
|
|
25
|
+
export type StateActionWriter<T> = {
|
|
26
|
+
write(value: T): void;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* StateAction keeps the latest state per writer and merges them on read.
|
|
30
|
+
* Values persist until the writer is disposed (abortSignal aborts).
|
|
31
|
+
*/
|
|
32
|
+
export declare class StateAction<T = unknown> {
|
|
33
|
+
private readonly mergeWriters?;
|
|
34
|
+
private readonly neutral?;
|
|
35
|
+
private readonly absoluteActions;
|
|
36
|
+
constructor(mergeWriters?: ((...values: Array<T>) => T) | undefined, neutral?: T | undefined);
|
|
37
|
+
createWriter(abortSignal: AbortSignal): StateActionWriter<T>;
|
|
38
|
+
get(): T;
|
|
39
|
+
mapFrom<S>(fn: (value: S) => T): StateAction<S>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
export class EventAction {
|
|
2
|
+
combine;
|
|
3
|
+
neutral;
|
|
4
|
+
latestTime = -Infinity;
|
|
5
|
+
subscriptionListeners = new Set();
|
|
6
|
+
readers = new Set();
|
|
7
|
+
constructor(combine, neutral) {
|
|
8
|
+
this.combine = combine;
|
|
9
|
+
this.neutral = neutral;
|
|
10
|
+
}
|
|
11
|
+
emit(value) {
|
|
12
|
+
this.latestTime = performance.now() / 1000;
|
|
13
|
+
for (const listener of this.subscriptionListeners) {
|
|
14
|
+
listener(value);
|
|
15
|
+
}
|
|
16
|
+
for (const reader of this.readers) {
|
|
17
|
+
reader.next.push(value);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
subscribe(callback, options) {
|
|
21
|
+
const listener = (value) => {
|
|
22
|
+
if (options?.once === true) {
|
|
23
|
+
this.subscriptionListeners.delete(listener);
|
|
24
|
+
}
|
|
25
|
+
callback(value);
|
|
26
|
+
};
|
|
27
|
+
this.subscriptionListeners.add(listener);
|
|
28
|
+
if (options?.signal != null) {
|
|
29
|
+
options.signal.addEventListener('abort', () => this.subscriptionListeners.delete(listener), { once: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
waitFor(signal) {
|
|
33
|
+
return new Promise((resolve) => this.subscribe(resolve, { once: true, signal }));
|
|
34
|
+
}
|
|
35
|
+
getLatestTime() {
|
|
36
|
+
return this.latestTime;
|
|
37
|
+
}
|
|
38
|
+
createReader(abortSignal) {
|
|
39
|
+
const reader = { next: new Array(), current: new Array() };
|
|
40
|
+
this.readers.add(reader);
|
|
41
|
+
abortSignal.addEventListener('abort', () => {
|
|
42
|
+
this.readers.delete(reader);
|
|
43
|
+
}, { once: true });
|
|
44
|
+
return {
|
|
45
|
+
update: () => {
|
|
46
|
+
reader.current.length = 0;
|
|
47
|
+
if (reader.next.length) {
|
|
48
|
+
reader.current.push(...reader.next);
|
|
49
|
+
reader.next.length = 0;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
get: () => {
|
|
53
|
+
if (this.combine == null) {
|
|
54
|
+
throw new Error(`unable to use a reader without providing a combine function`);
|
|
55
|
+
}
|
|
56
|
+
if (reader.current.length === 0) {
|
|
57
|
+
return this.neutral;
|
|
58
|
+
}
|
|
59
|
+
return this.combine(...reader.current);
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
mapFrom(fn, abortSignal) {
|
|
64
|
+
const action = new EventAction();
|
|
65
|
+
action.subscribe((value) => this.emit(fn(value)), { signal: abortSignal });
|
|
66
|
+
return action;
|
|
67
|
+
}
|
|
68
|
+
filterFrom(fn, abortSignal) {
|
|
69
|
+
const action = new EventAction();
|
|
70
|
+
action.subscribe((value) => void (fn(value) && this.emit(value)), { signal: abortSignal });
|
|
71
|
+
return action;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* StateAction keeps the latest state per writer and merges them on read.
|
|
76
|
+
* Values persist until the writer is disposed (abortSignal aborts).
|
|
77
|
+
*/
|
|
78
|
+
export class StateAction {
|
|
79
|
+
mergeWriters;
|
|
80
|
+
neutral;
|
|
81
|
+
absoluteActions = new Map();
|
|
82
|
+
constructor(mergeWriters, neutral) {
|
|
83
|
+
this.mergeWriters = mergeWriters;
|
|
84
|
+
this.neutral = neutral;
|
|
85
|
+
}
|
|
86
|
+
createWriter(abortSignal) {
|
|
87
|
+
const emitterUuid = globalThis.crypto?.randomUUID?.() ?? Math.random().toString(36).slice(2);
|
|
88
|
+
abortSignal.addEventListener('abort', () => this.absoluteActions.delete(emitterUuid), { once: true });
|
|
89
|
+
return {
|
|
90
|
+
write: (value) => {
|
|
91
|
+
if (!abortSignal.aborted) {
|
|
92
|
+
this.absoluteActions.set(emitterUuid, value);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
get() {
|
|
98
|
+
if (this.mergeWriters == null) {
|
|
99
|
+
throw new Error(`unable to read from a state action without providing a merge function`);
|
|
100
|
+
}
|
|
101
|
+
const values = [...this.absoluteActions.values()];
|
|
102
|
+
return values.length ? this.mergeWriters(...values) : this.neutral;
|
|
103
|
+
}
|
|
104
|
+
mapFrom(fn) {
|
|
105
|
+
const action = new StateAction();
|
|
106
|
+
action.createWriter = (abortSignal) => {
|
|
107
|
+
const writer = this.createWriter(abortSignal);
|
|
108
|
+
return {
|
|
109
|
+
write: (value) => writer.write(fn(value)),
|
|
110
|
+
};
|
|
111
|
+
};
|
|
112
|
+
return action;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { EventAction, StateAction } from './action.js';
|
|
2
|
+
export declare function BooleanOr(...values: Array<boolean>): boolean;
|
|
3
|
+
export declare const MoveForwardAction: StateAction<number>;
|
|
4
|
+
export declare const MoveBackwardAction: StateAction<number>;
|
|
5
|
+
export declare const MoveLeftAction: StateAction<number>;
|
|
6
|
+
export declare const MoveRightAction: StateAction<number>;
|
|
7
|
+
export declare const RunAction: StateAction<boolean>;
|
|
8
|
+
export declare const JumpAction: EventAction<unknown>;
|
|
9
|
+
export declare const ZoomAction: EventAction<number>;
|
|
10
|
+
export declare const RotateYawAction: EventAction<number>;
|
|
11
|
+
export declare const RotatePitchAction: EventAction<number>;
|
|
12
|
+
export * from './pointer-lock.js';
|
|
13
|
+
export * from './pointer-capture.js';
|
|
14
|
+
export * from './pointer.js';
|
|
15
|
+
export * from './keyboard.js';
|
|
16
|
+
export * from './screen-joystick.js';
|
|
17
|
+
export * from './screen-jump-button.js';
|
|
18
|
+
export * from './action.js';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { EventAction, StateAction } from './action.js';
|
|
2
|
+
export function BooleanOr(...values) {
|
|
3
|
+
let value = false;
|
|
4
|
+
for (let i = 0; i < values.length; i++) {
|
|
5
|
+
value ||= values[i];
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
const sum = (...values) => values.reduce((a, b) => a + b, 0);
|
|
10
|
+
export const MoveForwardAction = new StateAction(Math.max, 0);
|
|
11
|
+
export const MoveBackwardAction = new StateAction(Math.max, 0);
|
|
12
|
+
export const MoveLeftAction = new StateAction(Math.max, 0);
|
|
13
|
+
export const MoveRightAction = new StateAction(Math.max, 0);
|
|
14
|
+
export const RunAction = new StateAction(BooleanOr, false);
|
|
15
|
+
export const JumpAction = new EventAction();
|
|
16
|
+
export const ZoomAction = new EventAction(sum, 0);
|
|
17
|
+
export const RotateYawAction = new EventAction(sum, 0);
|
|
18
|
+
export const RotatePitchAction = new EventAction(sum, 0);
|
|
19
|
+
export * from './pointer-lock.js';
|
|
20
|
+
export * from './pointer-capture.js';
|
|
21
|
+
export * from './pointer.js';
|
|
22
|
+
export * from './keyboard.js';
|
|
23
|
+
export * from './screen-joystick.js';
|
|
24
|
+
export * from './screen-jump-button.js';
|
|
25
|
+
export * from './action.js';
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { StateAction, WriteonlyEventAction } from './index.js';
|
|
2
|
+
export declare class KeyboardActionBinding {
|
|
3
|
+
private pressedKeys;
|
|
4
|
+
private writer?;
|
|
5
|
+
private _keys;
|
|
6
|
+
get keys(): Array<string>;
|
|
7
|
+
set keys(keys: Array<string>);
|
|
8
|
+
requiresPointerLock?: boolean;
|
|
9
|
+
private updateState;
|
|
10
|
+
constructor(action: WriteonlyEventAction<KeyboardEvent> | StateAction<boolean>, domElement: HTMLElement, abortSignal: AbortSignal);
|
|
11
|
+
}
|
|
12
|
+
export declare const DefaultMoveForwardKeys: string[];
|
|
13
|
+
export declare const DefaultMoveBackwardKeys: string[];
|
|
14
|
+
export declare const DefaultMoveLeftKeys: string[];
|
|
15
|
+
export declare const DefaultMoveRightKeys: string[];
|
|
16
|
+
export declare const DefaultRunKeys: string[];
|
|
17
|
+
export declare const DefaultJumpKeys: string[];
|
|
18
|
+
export declare class KeyboardLocomotionActionBindings {
|
|
19
|
+
moveForwardBinding: KeyboardActionBinding;
|
|
20
|
+
moveLeftBinding: KeyboardActionBinding;
|
|
21
|
+
moveRightBinding: KeyboardActionBinding;
|
|
22
|
+
moveBackwardBinding: KeyboardActionBinding;
|
|
23
|
+
jumpBinding: KeyboardActionBinding;
|
|
24
|
+
runBinding: KeyboardActionBinding;
|
|
25
|
+
constructor(domElement: HTMLElement, abortSignal: AbortSignal);
|
|
26
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { JumpAction, MoveBackwardAction, MoveForwardAction, MoveLeftAction, MoveRightAction, StateAction, RunAction, } from './index.js';
|
|
2
|
+
export class KeyboardActionBinding {
|
|
3
|
+
pressedKeys = new Set();
|
|
4
|
+
writer;
|
|
5
|
+
//options
|
|
6
|
+
_keys = [];
|
|
7
|
+
get keys() {
|
|
8
|
+
return this._keys;
|
|
9
|
+
}
|
|
10
|
+
set keys(keys) {
|
|
11
|
+
this._keys = keys;
|
|
12
|
+
this.updateState();
|
|
13
|
+
}
|
|
14
|
+
requiresPointerLock;
|
|
15
|
+
updateState() {
|
|
16
|
+
this.writer?.write(this.keys.some((key) => this.pressedKeys.has(key)));
|
|
17
|
+
}
|
|
18
|
+
constructor(action, domElement, abortSignal) {
|
|
19
|
+
if (action instanceof StateAction) {
|
|
20
|
+
this.writer = action.createWriter(abortSignal);
|
|
21
|
+
domElement.tabIndex = 0;
|
|
22
|
+
domElement.addEventListener('keydown', (e) => {
|
|
23
|
+
if ((this.requiresPointerLock ?? false) && document.pointerLockElement != domElement) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.pressedKeys.add(e.code);
|
|
27
|
+
this.updateState();
|
|
28
|
+
}, { signal: abortSignal });
|
|
29
|
+
domElement.addEventListener('keyup', (e) => {
|
|
30
|
+
console.log('up');
|
|
31
|
+
this.pressedKeys.delete(e.code);
|
|
32
|
+
this.updateState();
|
|
33
|
+
}, { signal: abortSignal });
|
|
34
|
+
// Handle focus loss to clear pressed keys
|
|
35
|
+
const onBlur = () => {
|
|
36
|
+
console.log('blur');
|
|
37
|
+
this.pressedKeys.clear();
|
|
38
|
+
this.updateState();
|
|
39
|
+
};
|
|
40
|
+
domElement.addEventListener('blur', onBlur, { signal: abortSignal });
|
|
41
|
+
domElement.addEventListener('visibilitychange', () => {
|
|
42
|
+
if (document.visibilityState !== 'visible') {
|
|
43
|
+
onBlur();
|
|
44
|
+
}
|
|
45
|
+
}, { signal: abortSignal });
|
|
46
|
+
window.addEventListener('blur', onBlur, { signal: abortSignal });
|
|
47
|
+
document.addEventListener('pointerlockchange', () => {
|
|
48
|
+
if (document.pointerLockElement !== domElement) {
|
|
49
|
+
onBlur();
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
domElement.addEventListener('keydown', (e) => {
|
|
55
|
+
if (!this.keys.includes(e.code) || e.repeat) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
action.emit(e);
|
|
59
|
+
}, { signal: abortSignal });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
export const DefaultMoveForwardKeys = ['KeyW'];
|
|
63
|
+
export const DefaultMoveBackwardKeys = ['KeyS'];
|
|
64
|
+
export const DefaultMoveLeftKeys = ['KeyA'];
|
|
65
|
+
export const DefaultMoveRightKeys = ['KeyD'];
|
|
66
|
+
export const DefaultRunKeys = ['ShiftRight', 'ShiftLeft'];
|
|
67
|
+
export const DefaultJumpKeys = ['Space'];
|
|
68
|
+
export class KeyboardLocomotionActionBindings {
|
|
69
|
+
moveForwardBinding;
|
|
70
|
+
moveLeftBinding;
|
|
71
|
+
moveRightBinding;
|
|
72
|
+
moveBackwardBinding;
|
|
73
|
+
jumpBinding;
|
|
74
|
+
runBinding;
|
|
75
|
+
constructor(domElement, abortSignal) {
|
|
76
|
+
this.moveForwardBinding = new KeyboardActionBinding(MoveForwardAction.mapFrom((isPressed) => (isPressed ? 1 : 0)), domElement, abortSignal);
|
|
77
|
+
this.moveForwardBinding.keys = DefaultMoveForwardKeys;
|
|
78
|
+
this.moveLeftBinding = new KeyboardActionBinding(MoveLeftAction.mapFrom((isPressed) => (isPressed ? 1 : 0)), domElement, abortSignal);
|
|
79
|
+
this.moveLeftBinding.keys = DefaultMoveLeftKeys;
|
|
80
|
+
this.moveRightBinding = new KeyboardActionBinding(MoveRightAction.mapFrom((isPressed) => (isPressed ? 1 : 0)), domElement, abortSignal);
|
|
81
|
+
this.moveRightBinding.keys = DefaultMoveRightKeys;
|
|
82
|
+
this.moveBackwardBinding = new KeyboardActionBinding(MoveBackwardAction.mapFrom((isPressed) => (isPressed ? 1 : 0)), domElement, abortSignal);
|
|
83
|
+
this.moveBackwardBinding.keys = DefaultMoveBackwardKeys;
|
|
84
|
+
this.jumpBinding = new KeyboardActionBinding(JumpAction, domElement, abortSignal);
|
|
85
|
+
this.jumpBinding.keys = DefaultJumpKeys;
|
|
86
|
+
this.runBinding = new KeyboardActionBinding(RunAction, domElement, abortSignal);
|
|
87
|
+
this.runBinding.keys = DefaultRunKeys;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -1,16 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
* @requires to manually execute `domElement.setPointerCapture(pointerId)` on pointerdown
|
|
4
|
-
*/
|
|
5
|
-
export class PointerCaptureInput {
|
|
1
|
+
import { RotatePitchAction, RotateYawAction, ZoomAction } from './index.js';
|
|
2
|
+
export class PointerCaptureRotateZoomActionBindings {
|
|
6
3
|
domElement;
|
|
7
|
-
abortController = new AbortController();
|
|
8
|
-
deltaZoom = 0;
|
|
9
|
-
deltaYaw = 0;
|
|
10
|
-
deltaPitch = 0;
|
|
11
4
|
activePointers = new Map();
|
|
12
5
|
lastPinchDist = null;
|
|
13
|
-
|
|
6
|
+
rotationSpeed; // default 0.4
|
|
7
|
+
zoomSpeed; // default 0.0001
|
|
8
|
+
constructor(domElement, abortSignal) {
|
|
14
9
|
this.domElement = domElement;
|
|
15
10
|
domElement.addEventListener('pointerdown', (event) => {
|
|
16
11
|
this.domElement.setPointerCapture(event.pointerId);
|
|
@@ -20,7 +15,7 @@ export class PointerCaptureInput {
|
|
|
20
15
|
this.lastPinchDist = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
|
|
21
16
|
}
|
|
22
17
|
}, {
|
|
23
|
-
signal:
|
|
18
|
+
signal: abortSignal,
|
|
24
19
|
});
|
|
25
20
|
domElement.addEventListener('pointermove', (event) => {
|
|
26
21
|
if (!this.domElement.hasPointerCapture(event.pointerId)) {
|
|
@@ -31,16 +26,18 @@ export class PointerCaptureInput {
|
|
|
31
26
|
const pts = Array.from(this.activePointers.values());
|
|
32
27
|
if (this.lastPinchDist != null) {
|
|
33
28
|
const d = Math.hypot(pts[0].x - pts[1].x, pts[0].y - pts[1].y);
|
|
34
|
-
|
|
29
|
+
const zoomSpeed = this.zoomSpeed ?? 0.0001;
|
|
30
|
+
ZoomAction.emit((this.lastPinchDist - d) * zoomSpeed);
|
|
35
31
|
this.lastPinchDist = d;
|
|
36
32
|
}
|
|
37
33
|
event.preventDefault();
|
|
38
34
|
return;
|
|
39
35
|
}
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
const rotationSpeed = this.rotationSpeed ?? 0.4;
|
|
37
|
+
RotateYawAction.emit(-(event.movementX / window.innerHeight) * rotationSpeed);
|
|
38
|
+
RotatePitchAction.emit(-(event.movementY / window.innerHeight) * rotationSpeed);
|
|
42
39
|
}, {
|
|
43
|
-
signal:
|
|
40
|
+
signal: abortSignal,
|
|
44
41
|
});
|
|
45
42
|
domElement.addEventListener('pointerup', (event) => {
|
|
46
43
|
this.domElement.releasePointerCapture(event.pointerId);
|
|
@@ -49,7 +46,7 @@ export class PointerCaptureInput {
|
|
|
49
46
|
this.lastPinchDist = null;
|
|
50
47
|
}
|
|
51
48
|
}, {
|
|
52
|
-
signal:
|
|
49
|
+
signal: abortSignal,
|
|
53
50
|
});
|
|
54
51
|
domElement.addEventListener('pointercancel', (event) => {
|
|
55
52
|
this.domElement.releasePointerCapture(event.pointerId);
|
|
@@ -58,36 +55,14 @@ export class PointerCaptureInput {
|
|
|
58
55
|
this.lastPinchDist = null;
|
|
59
56
|
}
|
|
60
57
|
}, {
|
|
61
|
-
signal:
|
|
58
|
+
signal: abortSignal,
|
|
62
59
|
});
|
|
63
60
|
domElement.addEventListener('wheel', (event) => {
|
|
64
61
|
event.preventDefault();
|
|
65
|
-
this.
|
|
62
|
+
const zoomSpeed = this.zoomSpeed ?? 0.0001;
|
|
63
|
+
ZoomAction.emit(event.deltaY * zoomSpeed);
|
|
66
64
|
}, {
|
|
67
|
-
signal:
|
|
65
|
+
signal: abortSignal,
|
|
68
66
|
});
|
|
69
67
|
}
|
|
70
|
-
get(field, options) {
|
|
71
|
-
const rotationSpeed = options.pointerCaptureRotationSpeed ?? 0.4;
|
|
72
|
-
const zoomSpeed = options.pointerCaptureZoomSpeed ?? 0.0001;
|
|
73
|
-
let result;
|
|
74
|
-
switch (field) {
|
|
75
|
-
case DeltaPitchField:
|
|
76
|
-
result = (this.deltaPitch * rotationSpeed);
|
|
77
|
-
this.deltaPitch = 0;
|
|
78
|
-
break;
|
|
79
|
-
case DeltaYawField:
|
|
80
|
-
result = (this.deltaYaw * rotationSpeed);
|
|
81
|
-
this.deltaYaw = 0;
|
|
82
|
-
break;
|
|
83
|
-
case DeltaZoomField:
|
|
84
|
-
result = (this.deltaZoom * zoomSpeed);
|
|
85
|
-
this.deltaZoom = 0;
|
|
86
|
-
break;
|
|
87
|
-
}
|
|
88
|
-
return result;
|
|
89
|
-
}
|
|
90
|
-
dispose() {
|
|
91
|
-
this.abortController.abort();
|
|
92
|
-
}
|
|
93
68
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { RotatePitchAction, RotateYawAction, ZoomAction } from './index.js';
|
|
2
|
+
export class PointerLockRotateZoomActionBindings {
|
|
3
|
+
rotationSpeed; // default 0.4
|
|
4
|
+
zoomSpeed; // default 0.0001
|
|
5
|
+
lockOnClick;
|
|
6
|
+
constructor(domElement, abortSignal) {
|
|
7
|
+
// lock on click (only left click)
|
|
8
|
+
domElement.addEventListener('mousedown', (event) => {
|
|
9
|
+
const lockOnClick = this.lockOnClick ?? true;
|
|
10
|
+
if (!lockOnClick) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
// 0 = primary/left button
|
|
14
|
+
if (event.button !== 0) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (document.pointerLockElement === domElement) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
// request pointer lock as a user-gesture
|
|
21
|
+
if (domElement.requestPointerLock) {
|
|
22
|
+
domElement.requestPointerLock();
|
|
23
|
+
}
|
|
24
|
+
}, {
|
|
25
|
+
signal: abortSignal,
|
|
26
|
+
});
|
|
27
|
+
domElement.addEventListener('pointermove', (event) => {
|
|
28
|
+
if (document.pointerLockElement != domElement) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const rect = domElement.getBoundingClientRect();
|
|
32
|
+
const rotationSpeed = this.rotationSpeed ?? 0.4;
|
|
33
|
+
RotateYawAction.emit(-(event.movementX / rect.height) * rotationSpeed);
|
|
34
|
+
RotatePitchAction.emit(-(event.movementY / rect.height) * rotationSpeed);
|
|
35
|
+
}, {
|
|
36
|
+
signal: abortSignal,
|
|
37
|
+
});
|
|
38
|
+
domElement.addEventListener('wheel', (event) => {
|
|
39
|
+
if (document.pointerLockElement != domElement) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const zoomSpeed = this.zoomSpeed ?? 0.0001;
|
|
43
|
+
ZoomAction.emit(event.deltaY * zoomSpeed);
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
}, {
|
|
46
|
+
signal: abortSignal,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { StateAction, WriteonlyEventAction } from './index.js';
|
|
2
|
+
export declare class PointerButtonActionBinding {
|
|
3
|
+
private readonly domElement;
|
|
4
|
+
private writer?;
|
|
5
|
+
private eventAction?;
|
|
6
|
+
private buttonsMask;
|
|
7
|
+
private wasPressed;
|
|
8
|
+
private _buttons;
|
|
9
|
+
/**
|
|
10
|
+
* When undefined, any pressed button will set the state to true.
|
|
11
|
+
*/
|
|
12
|
+
get buttons(): Array<number> | undefined;
|
|
13
|
+
set buttons(buttons: Array<number> | undefined);
|
|
14
|
+
requiresPointerLock?: boolean;
|
|
15
|
+
private getPressedButtonsFromMask;
|
|
16
|
+
private processPointer;
|
|
17
|
+
constructor(action: WriteonlyEventAction<PointerEvent> | StateAction<boolean>, domElement: HTMLElement, abortSignal: AbortSignal);
|
|
18
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { StateAction } from './index.js';
|
|
2
|
+
export class PointerButtonActionBinding {
|
|
3
|
+
domElement;
|
|
4
|
+
writer;
|
|
5
|
+
eventAction;
|
|
6
|
+
buttonsMask = 0;
|
|
7
|
+
wasPressed = false;
|
|
8
|
+
// options
|
|
9
|
+
_buttons;
|
|
10
|
+
/**
|
|
11
|
+
* When undefined, any pressed button will set the state to true.
|
|
12
|
+
*/
|
|
13
|
+
get buttons() {
|
|
14
|
+
return this._buttons;
|
|
15
|
+
}
|
|
16
|
+
set buttons(buttons) {
|
|
17
|
+
this._buttons = buttons;
|
|
18
|
+
this.processPointer();
|
|
19
|
+
}
|
|
20
|
+
requiresPointerLock;
|
|
21
|
+
getPressedButtonsFromMask(mask) {
|
|
22
|
+
const pressed = [];
|
|
23
|
+
if (mask & 1)
|
|
24
|
+
pressed.push(0);
|
|
25
|
+
if (mask & 2)
|
|
26
|
+
pressed.push(2);
|
|
27
|
+
if (mask & 4)
|
|
28
|
+
pressed.push(1);
|
|
29
|
+
if (mask & 8)
|
|
30
|
+
pressed.push(3);
|
|
31
|
+
if (mask & 16)
|
|
32
|
+
pressed.push(4);
|
|
33
|
+
return pressed;
|
|
34
|
+
}
|
|
35
|
+
processPointer(e) {
|
|
36
|
+
if (e != null) {
|
|
37
|
+
this.buttonsMask = e.buttons;
|
|
38
|
+
}
|
|
39
|
+
if ((this.requiresPointerLock ?? false) && document.pointerLockElement != this.domElement) {
|
|
40
|
+
this.buttonsMask = 0;
|
|
41
|
+
}
|
|
42
|
+
const pressedButtons = this.getPressedButtonsFromMask(this.buttonsMask);
|
|
43
|
+
const isActive = this.buttons == null ? pressedButtons.length > 0 : this.buttons.some((btn) => pressedButtons.includes(btn));
|
|
44
|
+
if (this.writer != null) {
|
|
45
|
+
this.writer.write(isActive);
|
|
46
|
+
}
|
|
47
|
+
if (this.eventAction != null && e != null) {
|
|
48
|
+
if (isActive && !this.wasPressed) {
|
|
49
|
+
this.eventAction.emit(e);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.wasPressed = isActive;
|
|
53
|
+
}
|
|
54
|
+
constructor(action, domElement, abortSignal) {
|
|
55
|
+
this.domElement = domElement;
|
|
56
|
+
if (action instanceof StateAction) {
|
|
57
|
+
this.writer = action.createWriter(abortSignal);
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
this.eventAction = action;
|
|
61
|
+
}
|
|
62
|
+
domElement.addEventListener('pointerdown', (e) => {
|
|
63
|
+
this.processPointer(e);
|
|
64
|
+
}, { signal: abortSignal });
|
|
65
|
+
domElement.addEventListener('pointermove', (e) => {
|
|
66
|
+
// Moving while multiple buttons are pressed is the only signal we get
|
|
67
|
+
// when a second button is pressed simultaneously.
|
|
68
|
+
this.processPointer(e);
|
|
69
|
+
}, { signal: abortSignal });
|
|
70
|
+
domElement.addEventListener('pointerup', (e) => {
|
|
71
|
+
this.processPointer(e);
|
|
72
|
+
}, { signal: abortSignal });
|
|
73
|
+
domElement.addEventListener('pointercancel', (e) => {
|
|
74
|
+
this.processPointer(e);
|
|
75
|
+
}, { signal: abortSignal });
|
|
76
|
+
domElement.addEventListener('pointerleave', (e) => {
|
|
77
|
+
this.processPointer(e);
|
|
78
|
+
}, { signal: abortSignal });
|
|
79
|
+
domElement.addEventListener('blur', () => {
|
|
80
|
+
// On blur, assume no buttons pressed.
|
|
81
|
+
this.buttonsMask = 0;
|
|
82
|
+
this.processPointer();
|
|
83
|
+
}, { signal: abortSignal });
|
|
84
|
+
document.addEventListener('pointerlockchange', () => {
|
|
85
|
+
if (document.pointerLockElement !== domElement) {
|
|
86
|
+
this.processPointer();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare class ScreenJoystickLocomotionActionBindings {
|
|
2
|
+
private abortSignal;
|
|
3
|
+
readonly root: HTMLDivElement;
|
|
4
|
+
private readonly handle;
|
|
5
|
+
private pointerId;
|
|
6
|
+
private readonly forwardWriter;
|
|
7
|
+
private readonly backwardWriter;
|
|
8
|
+
private readonly leftWriter;
|
|
9
|
+
private readonly rightWriter;
|
|
10
|
+
private readonly runWriter;
|
|
11
|
+
runDistancePx?: number;
|
|
12
|
+
deadZonePx?: number;
|
|
13
|
+
constructor(domElement: HTMLElement, abortSignal: AbortSignal);
|
|
14
|
+
private updateHandle;
|
|
15
|
+
private resetHandle;
|
|
16
|
+
}
|