@pmndrs/viverse 0.1.20 → 0.2.1
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/animation/bone-map.d.ts +4 -0
- package/dist/animation/bone-map.js +11 -0
- package/dist/animation/default.d.ts +9 -0
- package/dist/animation/default.js +25 -0
- package/dist/animation/index.d.ts +12 -19
- package/dist/animation/index.js +76 -72
- package/dist/animation/mask.d.ts +6 -0
- package/dist/animation/mask.js +53 -0
- package/dist/camera.d.ts +7 -7
- package/dist/camera.js +33 -26
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/input/action.d.ts +41 -0
- package/dist/input/action.js +97 -0
- package/dist/input/index.d.ts +12 -27
- package/dist/input/index.js +17 -69
- package/dist/input/keyboard.d.ts +29 -7
- package/dist/input/keyboard.js +84 -72
- package/dist/input/pointer-capture.d.ts +6 -11
- package/dist/input/pointer-capture.js +10 -30
- package/dist/input/pointer-lock.d.ts +6 -11
- package/dist/input/pointer-lock.js +8 -29
- package/dist/input/screen-joystick.d.ts +12 -11
- package/dist/input/screen-joystick.js +39 -49
- package/dist/input/screen-jump-button.d.ts +2 -4
- package/dist/input/screen-jump-button.js +7 -12
- package/dist/model/index.d.ts +9 -13
- package/dist/model/index.js +12 -34
- package/dist/physics/index.d.ts +2 -5
- package/dist/physics/index.js +7 -16
- package/dist/simple-character/apply-input-options.d.ts +2 -0
- package/dist/simple-character/apply-input-options.js +28 -0
- package/dist/simple-character/defaults.d.ts +2 -0
- package/dist/simple-character/defaults.js +2 -0
- package/dist/simple-character/index.d.ts +116 -0
- package/dist/simple-character/index.js +104 -0
- package/dist/simple-character/state/index.d.ts +6 -0
- package/dist/simple-character/state/index.js +6 -0
- package/dist/simple-character/state/jump-down.d.ts +3 -0
- package/dist/simple-character/state/jump-down.js +26 -0
- package/dist/simple-character/state/jump-forward.d.ts +5 -0
- package/dist/simple-character/state/jump-forward.js +41 -0
- package/dist/simple-character/state/jump-loop.d.ts +3 -0
- package/dist/simple-character/state/jump-loop.js +24 -0
- package/dist/simple-character/state/jump-start.d.ts +4 -0
- package/dist/simple-character/state/jump-start.js +30 -0
- package/dist/simple-character/state/jump-up.d.ts +5 -0
- package/dist/simple-character/state/jump-up.js +40 -0
- package/dist/simple-character/state/movement.d.ts +3 -0
- package/dist/simple-character/state/movement.js +63 -0
- package/dist/simple-character/update-input-velocity.d.ts +4 -0
- package/dist/simple-character/update-input-velocity.js +25 -0
- package/dist/simple-character/update-rotation.d.ts +6 -0
- package/dist/simple-character/update-rotation.js +40 -0
- package/dist/utils.d.ts +12 -5
- package/dist/utils.js +37 -39
- 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/simple-character.d.ts +0 -107
- package/dist/simple-character.js +0 -344
|
@@ -1,21 +1,22 @@
|
|
|
1
|
-
import { Input, InputField } from './index.js';
|
|
2
1
|
export type ScreenJoystickInputOptions = {
|
|
3
2
|
screenJoystickRunDistancePx?: number;
|
|
4
3
|
screenJoystickDeadZonePx?: number;
|
|
5
4
|
};
|
|
6
|
-
export declare class ScreenJoystickInput
|
|
7
|
-
private readonly
|
|
5
|
+
export declare class ScreenJoystickInput {
|
|
6
|
+
private readonly abortController;
|
|
8
7
|
readonly root: HTMLDivElement;
|
|
9
8
|
private readonly handle;
|
|
10
|
-
private moveX;
|
|
11
|
-
private moveY;
|
|
12
|
-
private running;
|
|
13
|
-
private readonly joystickRadius;
|
|
14
|
-
private joyCenterX;
|
|
15
|
-
private joyCenterY;
|
|
16
9
|
private pointerId;
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
private forwardWriter;
|
|
11
|
+
private backwardWriter;
|
|
12
|
+
private leftWriter;
|
|
13
|
+
private rightWriter;
|
|
14
|
+
private runWriter;
|
|
15
|
+
options: {
|
|
16
|
+
runDistancePx?: number;
|
|
17
|
+
deadZonePx?: number;
|
|
18
|
+
};
|
|
19
|
+
constructor(domElement: HTMLElement);
|
|
19
20
|
dispose(): void;
|
|
20
21
|
private updateHandle;
|
|
21
22
|
private resetHandle;
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MoveForwardAction, MoveBackwardAction, MoveLeftAction, MoveRightAction, RunAction } from './index.js';
|
|
2
2
|
const DefaultDeadZonePx = 24;
|
|
3
3
|
const DefaultRunDistancePx = 46;
|
|
4
|
+
const JoystickRadius = 56;
|
|
4
5
|
export class ScreenJoystickInput {
|
|
5
|
-
|
|
6
|
+
abortController = new AbortController();
|
|
6
7
|
root;
|
|
7
8
|
handle;
|
|
8
|
-
moveX = 0;
|
|
9
|
-
moveY = 0;
|
|
10
|
-
running = false;
|
|
11
|
-
joystickRadius = 56;
|
|
12
|
-
joyCenterX = 0;
|
|
13
|
-
joyCenterY = 0;
|
|
14
9
|
pointerId;
|
|
15
|
-
|
|
16
|
-
|
|
10
|
+
forwardWriter = MoveForwardAction.createWriter(this.abortController.signal);
|
|
11
|
+
backwardWriter = MoveBackwardAction.createWriter(this.abortController.signal);
|
|
12
|
+
leftWriter = MoveLeftAction.createWriter(this.abortController.signal);
|
|
13
|
+
rightWriter = MoveRightAction.createWriter(this.abortController.signal);
|
|
14
|
+
runWriter = RunAction.createWriter(this.abortController.signal);
|
|
15
|
+
options = {};
|
|
16
|
+
constructor(domElement) {
|
|
17
17
|
const parent = domElement.parentElement ?? domElement;
|
|
18
18
|
const joy = document.createElement('div');
|
|
19
19
|
joy.className = 'viverse-joystick mobile-only';
|
|
@@ -58,9 +58,9 @@ export class ScreenJoystickInput {
|
|
|
58
58
|
joy.setPointerCapture(e.pointerId);
|
|
59
59
|
this.pointerId = e.pointerId;
|
|
60
60
|
const rect = joy.getBoundingClientRect();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
this.updateHandle(e.clientX -
|
|
61
|
+
const joyCenterX = rect.left + rect.width / 2;
|
|
62
|
+
const joyCenterY = rect.top + rect.height / 2;
|
|
63
|
+
this.updateHandle(e.clientX - joyCenterX, e.clientY - joyCenterY);
|
|
64
64
|
};
|
|
65
65
|
const onPointerMove = (e) => {
|
|
66
66
|
if (this.pointerId == null) {
|
|
@@ -68,7 +68,10 @@ export class ScreenJoystickInput {
|
|
|
68
68
|
}
|
|
69
69
|
e.preventDefault();
|
|
70
70
|
e.stopPropagation();
|
|
71
|
-
|
|
71
|
+
const rect = joy.getBoundingClientRect();
|
|
72
|
+
const joyCenterX = rect.left + rect.width / 2;
|
|
73
|
+
const joyCenterY = rect.top + rect.height / 2;
|
|
74
|
+
this.updateHandle(e.clientX - joyCenterX, e.clientY - joyCenterY);
|
|
72
75
|
};
|
|
73
76
|
const onPointerEnd = (e) => {
|
|
74
77
|
if (this.pointerId != e.pointerId) {
|
|
@@ -79,49 +82,36 @@ export class ScreenJoystickInput {
|
|
|
79
82
|
e.preventDefault();
|
|
80
83
|
this.resetHandle();
|
|
81
84
|
};
|
|
82
|
-
joy.addEventListener('pointerdown', onPointerDown);
|
|
83
|
-
joy.addEventListener('pointermove', onPointerMove);
|
|
84
|
-
joy.addEventListener('pointerup', onPointerEnd);
|
|
85
|
-
joy.addEventListener('pointercancel', onPointerEnd);
|
|
86
|
-
}
|
|
87
|
-
get(field) {
|
|
88
|
-
switch (field) {
|
|
89
|
-
case MoveForwardField:
|
|
90
|
-
return Math.max(0, this.moveY);
|
|
91
|
-
case MoveBackwardField:
|
|
92
|
-
return Math.max(0, -this.moveY);
|
|
93
|
-
case MoveLeftField:
|
|
94
|
-
return Math.max(0, -this.moveX);
|
|
95
|
-
case MoveRightField:
|
|
96
|
-
return Math.max(0, this.moveX);
|
|
97
|
-
case RunField:
|
|
98
|
-
return this.running;
|
|
99
|
-
}
|
|
100
|
-
return undefined;
|
|
85
|
+
joy.addEventListener('pointerdown', onPointerDown, { signal: this.abortController.signal });
|
|
86
|
+
joy.addEventListener('pointermove', onPointerMove, { signal: this.abortController.signal });
|
|
87
|
+
joy.addEventListener('pointerup', onPointerEnd, { signal: this.abortController.signal });
|
|
88
|
+
joy.addEventListener('pointercancel', onPointerEnd, { signal: this.abortController.signal });
|
|
101
89
|
}
|
|
102
90
|
dispose() {
|
|
91
|
+
this.abortController.abort();
|
|
103
92
|
this.root.remove();
|
|
104
93
|
}
|
|
105
94
|
updateHandle(dx, dy) {
|
|
106
|
-
const
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
const clampedY = (dy / len) * Math.min(len, max);
|
|
95
|
+
const distanceToCenter = Math.hypot(dx, dy) || 1;
|
|
96
|
+
const clampedX = (dx / distanceToCenter) * Math.min(distanceToCenter, JoystickRadius);
|
|
97
|
+
const clampedY = (dy / distanceToCenter) * Math.min(distanceToCenter, JoystickRadius);
|
|
110
98
|
this.handle.style.transform = `translate(-50%,-50%) translate(${clampedX}px, ${clampedY}px)`;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
this.
|
|
99
|
+
const deadZone = this.options.deadZonePx ?? DefaultDeadZonePx;
|
|
100
|
+
const runDistance = this.options.runDistancePx ?? DefaultRunDistancePx;
|
|
101
|
+
const moveY = distanceToCenter <= deadZone ? 0 : -clampedY / JoystickRadius;
|
|
102
|
+
const moveX = distanceToCenter <= deadZone ? 0 : clampedX / JoystickRadius;
|
|
103
|
+
this.forwardWriter.write(Math.max(0, moveY));
|
|
104
|
+
this.backwardWriter.write(Math.max(0, -moveY));
|
|
105
|
+
this.leftWriter.write(Math.max(0, -moveX));
|
|
106
|
+
this.rightWriter.write(Math.max(0, moveX));
|
|
107
|
+
this.runWriter.write(distanceToCenter > runDistance);
|
|
120
108
|
}
|
|
121
109
|
resetHandle() {
|
|
122
110
|
this.handle.style.transform = 'translate(-50%,-50%)';
|
|
123
|
-
this.
|
|
124
|
-
this.
|
|
125
|
-
this.
|
|
111
|
+
this.forwardWriter.write(0);
|
|
112
|
+
this.backwardWriter.write(0);
|
|
113
|
+
this.leftWriter.write(0);
|
|
114
|
+
this.rightWriter.write(0);
|
|
115
|
+
this.runWriter.write(false);
|
|
126
116
|
}
|
|
127
117
|
}
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export declare class ScreenJumpButtonInput {
|
|
2
|
+
private readonly abortController;
|
|
3
3
|
readonly root: HTMLDivElement;
|
|
4
|
-
private lastJumpTime;
|
|
5
4
|
constructor(domElement: HTMLElement);
|
|
6
|
-
get<T>(field: InputField<T>): T | undefined;
|
|
7
5
|
dispose(): void;
|
|
8
6
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JumpAction } from './index.js';
|
|
2
2
|
export class ScreenJumpButtonInput {
|
|
3
|
+
abortController = new AbortController();
|
|
3
4
|
root;
|
|
4
|
-
lastJumpTime = null;
|
|
5
5
|
constructor(domElement) {
|
|
6
6
|
const parent = domElement.parentElement ?? domElement;
|
|
7
7
|
const btn = document.createElement('div');
|
|
@@ -27,23 +27,18 @@ export class ScreenJumpButtonInput {
|
|
|
27
27
|
const onPress = (e) => {
|
|
28
28
|
e.preventDefault();
|
|
29
29
|
e.stopPropagation();
|
|
30
|
-
|
|
30
|
+
JumpAction.emit();
|
|
31
31
|
};
|
|
32
32
|
const stopPropagation = (e) => {
|
|
33
33
|
e.stopPropagation();
|
|
34
34
|
e.preventDefault();
|
|
35
35
|
};
|
|
36
|
-
this.root.addEventListener('pointerdown', onPress);
|
|
37
|
-
this.root.addEventListener('pointermove', stopPropagation);
|
|
38
|
-
this.root.addEventListener('pointerup', stopPropagation);
|
|
39
|
-
}
|
|
40
|
-
get(field) {
|
|
41
|
-
if (field === LastTimeJumpPressedField) {
|
|
42
|
-
return this.lastJumpTime;
|
|
43
|
-
}
|
|
44
|
-
return undefined;
|
|
36
|
+
this.root.addEventListener('pointerdown', onPress, { signal: this.abortController.signal });
|
|
37
|
+
this.root.addEventListener('pointermove', stopPropagation, { signal: this.abortController.signal });
|
|
38
|
+
this.root.addEventListener('pointerup', stopPropagation, { signal: this.abortController.signal });
|
|
45
39
|
}
|
|
46
40
|
dispose() {
|
|
41
|
+
this.abortController.abort();
|
|
47
42
|
this.root.remove();
|
|
48
43
|
}
|
|
49
44
|
}
|
package/dist/model/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Quaternion } from 'three';
|
|
1
|
+
import { AnimationAction, AnimationMixer, Object3D, Quaternion } from 'three';
|
|
2
2
|
export { VRMHumanBoneName } from '@pixiv/three-vrm';
|
|
3
3
|
export * from './vrm.js';
|
|
4
4
|
export type CharacterModelOptions = {
|
|
@@ -17,16 +17,12 @@ export type CharacterModelOptions = {
|
|
|
17
17
|
* @default true
|
|
18
18
|
*/
|
|
19
19
|
readonly receiveShadow?: boolean;
|
|
20
|
-
}
|
|
21
|
-
export declare function
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}) | (import("three/examples/jsm/Addons.js").GLTF & {
|
|
27
|
-
scene: import("three").Object3D<import("three").Object3DEventMap & {
|
|
28
|
-
dispose: {};
|
|
29
|
-
}>;
|
|
30
|
-
})) & {
|
|
20
|
+
};
|
|
21
|
+
export declare function flattenCharacterModelOptions(options: Exclude<CharacterModelOptions, false> | undefined): Parameters<typeof loadCharacterModel>;
|
|
22
|
+
export type CharacterModel = {
|
|
23
|
+
mixer: AnimationMixer;
|
|
24
|
+
scene: Object3D;
|
|
25
|
+
currentAnimations: Map<string | undefined, AnimationAction>;
|
|
31
26
|
boneRotationOffset?: Quaternion;
|
|
32
|
-
}
|
|
27
|
+
};
|
|
28
|
+
export declare function loadCharacterModel(url?: string, type?: Exclude<CharacterModelOptions, boolean>['type'], boneRotationOffset?: Quaternion, castShadow?: boolean, receiveShadow?: boolean): Promise<CharacterModel>;
|
package/dist/model/index.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
import { Euler, Quaternion } from 'three';
|
|
2
|
-
import { loadVrmCharacterModel } from './vrm.js';
|
|
3
|
-
import { cached, clearCache } from '../utils.js';
|
|
1
|
+
import { AnimationMixer, Euler, Quaternion } from 'three';
|
|
4
2
|
import { loadGltfCharacterModel } from './gltf.js';
|
|
3
|
+
import { loadVrmCharacterModel } from './vrm.js';
|
|
5
4
|
export { VRMHumanBoneName } from '@pixiv/three-vrm';
|
|
6
5
|
export * from './vrm.js';
|
|
7
|
-
|
|
6
|
+
export function flattenCharacterModelOptions(options) {
|
|
7
|
+
if (options == null) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
return [options.url, options.type, options.boneRotationOffset, options.castShadow, options.receiveShadow];
|
|
11
|
+
}
|
|
12
|
+
export async function loadCharacterModel(url, type, boneRotationOffset, castShadow = true, receiveShadow = true) {
|
|
8
13
|
let result;
|
|
9
14
|
if (url == null) {
|
|
10
15
|
//prepare loading the default model
|
|
@@ -41,36 +46,9 @@ async function uncachedLoadCharacterModel(type, url, boneRotationOffset, castSha
|
|
|
41
46
|
obj.receiveShadow = true;
|
|
42
47
|
}
|
|
43
48
|
});
|
|
44
|
-
const
|
|
45
|
-
if (rootBone == null) {
|
|
46
|
-
throw new Error(`unable to load model - missing root bone`);
|
|
47
|
-
}
|
|
48
|
-
const restPose = rootBone.clone();
|
|
49
|
+
const restPose = result.scene.clone();
|
|
49
50
|
restPose.visible = false;
|
|
50
|
-
restPose.traverse((
|
|
51
|
+
restPose.traverse((object) => (object.name = `rest_${object.name}`));
|
|
51
52
|
result.scene.add(restPose);
|
|
52
|
-
return result;
|
|
53
|
-
}
|
|
54
|
-
function getCharacterModelDependencies(options = true) {
|
|
55
|
-
if (options === false) {
|
|
56
|
-
return undefined;
|
|
57
|
-
}
|
|
58
|
-
if (options === true) {
|
|
59
|
-
return [undefined, undefined, undefined, undefined, undefined];
|
|
60
|
-
}
|
|
61
|
-
return [options.type, options.url, options.boneRotationOffset, options.castShadow, options.receiveShadow];
|
|
62
|
-
}
|
|
63
|
-
export function clearCharacterModelCache(options) {
|
|
64
|
-
const dependencies = getCharacterModelDependencies(options);
|
|
65
|
-
if (dependencies == null) {
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
clearCache(uncachedLoadCharacterModel, dependencies);
|
|
69
|
-
}
|
|
70
|
-
export function loadCharacterModel(options) {
|
|
71
|
-
const dependencies = getCharacterModelDependencies(options);
|
|
72
|
-
if (dependencies == null) {
|
|
73
|
-
return undefined;
|
|
74
|
-
}
|
|
75
|
-
return cached(uncachedLoadCharacterModel, dependencies);
|
|
53
|
+
return Object.assign(result, { mixer: new AnimationMixer(result.scene), currentAnimations: new Map() });
|
|
76
54
|
}
|
package/dist/physics/index.d.ts
CHANGED
|
@@ -32,9 +32,7 @@ export type BvhCharacterPhysicsOptions = {
|
|
|
32
32
|
* assumes the target object origin is at its bottom
|
|
33
33
|
*/
|
|
34
34
|
export declare class BvhCharacterPhysics {
|
|
35
|
-
private readonly character;
|
|
36
35
|
private readonly world;
|
|
37
|
-
private disposed;
|
|
38
36
|
private readonly stateVelocity;
|
|
39
37
|
readonly inputVelocity: Vector3;
|
|
40
38
|
private notGroundedSeconds;
|
|
@@ -42,14 +40,13 @@ export declare class BvhCharacterPhysics {
|
|
|
42
40
|
private readonly aabbox;
|
|
43
41
|
private radius;
|
|
44
42
|
get isGrounded(): boolean;
|
|
45
|
-
constructor(
|
|
43
|
+
constructor(world: BvhPhysicsWorld);
|
|
46
44
|
applyVelocity(velocity: Vector3): void;
|
|
47
45
|
/**
|
|
48
46
|
* @param delta in seconds
|
|
49
47
|
*/
|
|
50
|
-
update(fullDelta: number, options?: BvhCharacterPhysicsOptions): void;
|
|
48
|
+
update(model: Object3D, fullDelta: number, options?: BvhCharacterPhysicsOptions): void;
|
|
51
49
|
private updateBoundingShapes;
|
|
52
|
-
dispose(): void;
|
|
53
50
|
shapecastCapsule(position: Vector3, maxGroundSlope: number, options: Exclude<BvhCharacterPhysicsOptions, boolean>): boolean;
|
|
54
51
|
}
|
|
55
52
|
export * from './world.js';
|
package/dist/physics/index.js
CHANGED
|
@@ -11,9 +11,7 @@ const YAxis = new Vector3(0, 1, 0);
|
|
|
11
11
|
* assumes the target object origin is at its bottom
|
|
12
12
|
*/
|
|
13
13
|
export class BvhCharacterPhysics {
|
|
14
|
-
character;
|
|
15
14
|
world;
|
|
16
|
-
disposed = false;
|
|
17
15
|
stateVelocity = new Vector3();
|
|
18
16
|
inputVelocity = new Vector3();
|
|
19
17
|
notGroundedSeconds = 0;
|
|
@@ -23,8 +21,7 @@ export class BvhCharacterPhysics {
|
|
|
23
21
|
get isGrounded() {
|
|
24
22
|
return this.notGroundedSeconds < 0.2;
|
|
25
23
|
}
|
|
26
|
-
constructor(
|
|
27
|
-
this.character = character;
|
|
24
|
+
constructor(world) {
|
|
28
25
|
this.world = world;
|
|
29
26
|
}
|
|
30
27
|
applyVelocity(velocity) {
|
|
@@ -33,16 +30,13 @@ export class BvhCharacterPhysics {
|
|
|
33
30
|
/**
|
|
34
31
|
* @param delta in seconds
|
|
35
32
|
*/
|
|
36
|
-
update(fullDelta, options = true) {
|
|
33
|
+
update(model, fullDelta, options = true) {
|
|
37
34
|
if (options === false) {
|
|
38
35
|
return;
|
|
39
36
|
}
|
|
40
37
|
if (options === true) {
|
|
41
38
|
options = {};
|
|
42
39
|
}
|
|
43
|
-
if (this.disposed) {
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
40
|
//at max catch up to 1 second of physics in one update call (running at less then 1fps is unplayable anyways)
|
|
47
41
|
fullDelta = Math.min(1, fullDelta);
|
|
48
42
|
const updatesPerSecond = options.updatesPerSecond ?? 60;
|
|
@@ -52,10 +46,10 @@ export class BvhCharacterPhysics {
|
|
|
52
46
|
const partialDelta = Math.min(fullDelta, physicsDelta);
|
|
53
47
|
fullDelta -= physicsDelta;
|
|
54
48
|
//compute global position and inverted parent matrix so that we can compute the position in global space and re-assign it to the local chracter space
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
position.copy(
|
|
58
|
-
invertedParentMatrix.copy(
|
|
49
|
+
if (model.parent != null) {
|
|
50
|
+
model.parent.updateWorldMatrix(true, false);
|
|
51
|
+
position.copy(model.position).applyMatrix4(model.parent.matrixWorld);
|
|
52
|
+
invertedParentMatrix.copy(model.parent.matrixWorld).invert();
|
|
59
53
|
}
|
|
60
54
|
else {
|
|
61
55
|
invertedParentMatrix.identity();
|
|
@@ -70,7 +64,7 @@ export class BvhCharacterPhysics {
|
|
|
70
64
|
this.notGroundedSeconds = 0;
|
|
71
65
|
}
|
|
72
66
|
if (!isGrounded || this.inputVelocity.lengthSq() > 0) {
|
|
73
|
-
|
|
67
|
+
model.position.copy(collisionFreePosition).applyMatrix4(invertedParentMatrix);
|
|
74
68
|
}
|
|
75
69
|
//compute new velocity
|
|
76
70
|
// apply gravity
|
|
@@ -101,9 +95,6 @@ export class BvhCharacterPhysics {
|
|
|
101
95
|
this.aabbox.min.addScalar(-this.radius);
|
|
102
96
|
this.aabbox.max.addScalar(this.radius);
|
|
103
97
|
}
|
|
104
|
-
dispose() {
|
|
105
|
-
this.disposed = true;
|
|
106
|
-
}
|
|
107
98
|
shapecastCapsule(position, maxGroundSlope, options) {
|
|
108
99
|
this.updateBoundingShapes(options);
|
|
109
100
|
let grounded = false;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DefaultJumpKeys, DefaultMoveBackwardKeys, DefaultMoveForwardKeys, DefaultMoveLeftKeys, DefaultMoveRightKeys, DefaultRunKeys, LocomotionKeyboardInput, } from '../input/keyboard.js';
|
|
2
|
+
import { PointerCaptureInput } from '../input/pointer-capture.js';
|
|
3
|
+
import { PointerLockInput } from '../input/pointer-lock.js';
|
|
4
|
+
import { ScreenJoystickInput } from '../input/screen-joystick.js';
|
|
5
|
+
export function applySimpleCharacterInputOptions(inputs, options) {
|
|
6
|
+
for (const input of inputs) {
|
|
7
|
+
if (input instanceof ScreenJoystickInput) {
|
|
8
|
+
input.options.deadZonePx = options?.screenJoystickDeadZonePx;
|
|
9
|
+
input.options.runDistancePx = options?.screenJoystickRunDistancePx;
|
|
10
|
+
}
|
|
11
|
+
if (input instanceof PointerCaptureInput) {
|
|
12
|
+
input.options.rotationSpeed = options?.pointerCaptureRotationSpeed;
|
|
13
|
+
input.options.zoomSpeed = options?.pointerCaptureZoomSpeed;
|
|
14
|
+
}
|
|
15
|
+
if (input instanceof PointerLockInput) {
|
|
16
|
+
input.options.rotationSpeed = options?.pointerLockRotationSpeed;
|
|
17
|
+
input.options.zoomSpeed = options?.pointerLockZoomSpeed;
|
|
18
|
+
}
|
|
19
|
+
if (input instanceof LocomotionKeyboardInput) {
|
|
20
|
+
input.forward.options.keys = options?.keyboardMoveForwardKeys ?? DefaultMoveForwardKeys;
|
|
21
|
+
input.backward.options.keys = options?.keyboardMoveBackwardKeys ?? DefaultMoveBackwardKeys;
|
|
22
|
+
input.left.options.keys = options?.keyboardMoveLeftKeys ?? DefaultMoveLeftKeys;
|
|
23
|
+
input.right.options.keys = options?.keyboardMoveRightKeys ?? DefaultMoveRightKeys;
|
|
24
|
+
input.run.options.keys = options?.keyboardRunKeys ?? DefaultRunKeys;
|
|
25
|
+
input.jump.options.keys = options?.keyboardJumpKeys ?? DefaultJumpKeys;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { Group, Object3D, Object3DEventMap, AnimationAction } from 'three';
|
|
2
|
+
import { CharacterAnimationOptions } from '../animation/index.js';
|
|
3
|
+
import { CharacterCameraBehavior, SimpleCharacterCameraBehaviorOptions } from '../camera.js';
|
|
4
|
+
import { CharacterModelOptions, CharacterModel } from '../model/index.js';
|
|
5
|
+
import { BvhCharacterPhysicsOptions, BvhCharacterPhysics, BvhPhysicsWorld } from '../physics/index.js';
|
|
6
|
+
export type SimpleCharacterState = {
|
|
7
|
+
camera: Object3D;
|
|
8
|
+
model?: CharacterModel;
|
|
9
|
+
physics: BvhCharacterPhysics;
|
|
10
|
+
lastJump: number;
|
|
11
|
+
};
|
|
12
|
+
export type SimpleCharacterMovementOptions = {
|
|
13
|
+
/**
|
|
14
|
+
* @default true
|
|
15
|
+
*/
|
|
16
|
+
jump?: {
|
|
17
|
+
/**
|
|
18
|
+
* @default 0.2
|
|
19
|
+
*/
|
|
20
|
+
delay?: number;
|
|
21
|
+
/**
|
|
22
|
+
* @default 0.1
|
|
23
|
+
*/
|
|
24
|
+
bufferTime?: number;
|
|
25
|
+
/**
|
|
26
|
+
* @default 8
|
|
27
|
+
*/
|
|
28
|
+
speed?: number;
|
|
29
|
+
} | boolean;
|
|
30
|
+
/**
|
|
31
|
+
* @default true
|
|
32
|
+
*/
|
|
33
|
+
walk?: {
|
|
34
|
+
speed?: number;
|
|
35
|
+
} | boolean;
|
|
36
|
+
/**
|
|
37
|
+
* @default true
|
|
38
|
+
*/
|
|
39
|
+
run?: {
|
|
40
|
+
speed?: number;
|
|
41
|
+
} | boolean;
|
|
42
|
+
};
|
|
43
|
+
export type SimpleCharacterAnimationOptions = {
|
|
44
|
+
readonly walk?: CharacterAnimationOptions;
|
|
45
|
+
readonly run?: CharacterAnimationOptions;
|
|
46
|
+
readonly idle?: CharacterAnimationOptions;
|
|
47
|
+
readonly jumpUp?: CharacterAnimationOptions;
|
|
48
|
+
readonly jumpLoop?: CharacterAnimationOptions;
|
|
49
|
+
readonly jumpDown?: CharacterAnimationOptions;
|
|
50
|
+
readonly jumpForward?: CharacterAnimationOptions;
|
|
51
|
+
/**
|
|
52
|
+
* @default "movement"
|
|
53
|
+
*/
|
|
54
|
+
yawRotationBasdOn?: 'camera' | 'movement';
|
|
55
|
+
/**
|
|
56
|
+
* @default 10
|
|
57
|
+
*/
|
|
58
|
+
maxYawRotationSpeed?: number;
|
|
59
|
+
/**
|
|
60
|
+
* @default 0.1
|
|
61
|
+
*/
|
|
62
|
+
crossFadeDuration?: number;
|
|
63
|
+
};
|
|
64
|
+
export type SimpleCharacterInputOptions = {
|
|
65
|
+
screenJoystickRunDistancePx?: number;
|
|
66
|
+
screenJoystickDeadZonePx?: number;
|
|
67
|
+
pointerCaptureRotationSpeed?: number;
|
|
68
|
+
pointerCaptureZoomSpeed?: number;
|
|
69
|
+
pointerLockRotationSpeed?: number;
|
|
70
|
+
pointerLockZoomSpeed?: number;
|
|
71
|
+
keyboardMoveForwardKeys?: Array<string>;
|
|
72
|
+
keyboardMoveBackwardKeys?: Array<string>;
|
|
73
|
+
keyboardMoveLeftKeys?: Array<string>;
|
|
74
|
+
keyboardMoveRightKeys?: Array<string>;
|
|
75
|
+
keyboardRunKeys?: Array<string>;
|
|
76
|
+
keyboardJumpKeys?: Array<string>;
|
|
77
|
+
};
|
|
78
|
+
export type SimpleCharacterOptions = {
|
|
79
|
+
readonly input?: ReadonlyArray<{
|
|
80
|
+
new (domElement: HTMLElement): {
|
|
81
|
+
dispose(): void;
|
|
82
|
+
};
|
|
83
|
+
}>;
|
|
84
|
+
inputOptions?: SimpleCharacterInputOptions;
|
|
85
|
+
movement?: SimpleCharacterMovementOptions;
|
|
86
|
+
readonly model?: CharacterModelOptions | boolean;
|
|
87
|
+
physics?: BvhCharacterPhysicsOptions;
|
|
88
|
+
cameraBehavior?: SimpleCharacterCameraBehaviorOptions;
|
|
89
|
+
readonly animation?: SimpleCharacterAnimationOptions;
|
|
90
|
+
};
|
|
91
|
+
export declare class SimpleCharacter extends Group<Object3DEventMap & {
|
|
92
|
+
loaded: {};
|
|
93
|
+
}> implements SimpleCharacterState {
|
|
94
|
+
readonly camera: Object3D;
|
|
95
|
+
private readonly world;
|
|
96
|
+
private readonly options;
|
|
97
|
+
readonly cameraBehavior: CharacterCameraBehavior;
|
|
98
|
+
readonly physics: BvhCharacterPhysics;
|
|
99
|
+
readonly currentAnimationRef: {
|
|
100
|
+
current?: AnimationAction;
|
|
101
|
+
};
|
|
102
|
+
model?: CharacterModel;
|
|
103
|
+
private readonly updateTimeline;
|
|
104
|
+
private readonly graph;
|
|
105
|
+
private readonly abortController;
|
|
106
|
+
private readonly inputs;
|
|
107
|
+
lastJump: number;
|
|
108
|
+
readonly abortSignal: AbortSignal;
|
|
109
|
+
constructor(camera: Object3D, world: BvhPhysicsWorld, domElement: HTMLElement, options?: SimpleCharacterOptions);
|
|
110
|
+
private init;
|
|
111
|
+
update(delta: number): void;
|
|
112
|
+
dispose(): void;
|
|
113
|
+
}
|
|
114
|
+
export * from './update-input-velocity.js';
|
|
115
|
+
export * from './update-rotation.js';
|
|
116
|
+
export * from './apply-input-options.js';
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { VRM, VRMUtils } from '@pixiv/three-vrm';
|
|
2
|
+
import { runTimeline, GraphTimeline } from '@pmndrs/timeline';
|
|
3
|
+
import { Group, Vector3 } from 'three';
|
|
4
|
+
import { CharacterCameraBehavior } from '../camera.js';
|
|
5
|
+
import { LocomotionKeyboardInput, PointerCaptureInput, ScreenJoystickInput, ScreenJumpButtonInput, } from '../input/index.js';
|
|
6
|
+
import { loadCharacterModel, flattenCharacterModelOptions, } from '../model/index.js';
|
|
7
|
+
import { BvhCharacterPhysics } from '../physics/index.js';
|
|
8
|
+
import { loadSimpleCharacterJumpDownState } from './state/jump-down.js';
|
|
9
|
+
import { loadSimpleCharacterJumpForwardAction, loadSimpleCharacterJumpForwardState } from './state/jump-forward.js';
|
|
10
|
+
import { loadSimpleCharacterJumpLoopState } from './state/jump-loop.js';
|
|
11
|
+
import { loadSimpleCharacterJumpStartState } from './state/jump-start.js';
|
|
12
|
+
import { loadSimpleCharacterJumpUpAction, loadSimpleCharacterJumpUpState } from './state/jump-up.js';
|
|
13
|
+
import { loadSimpleCharacterMovingState } from './state/movement.js';
|
|
14
|
+
import { updateSimpleCharacterInputVelocity } from './update-input-velocity.js';
|
|
15
|
+
import { updateSimpleCharacterRotation } from './update-rotation.js';
|
|
16
|
+
import { shouldJump } from '../utils.js';
|
|
17
|
+
import { applySimpleCharacterInputOptions } from './apply-input-options.js';
|
|
18
|
+
export class SimpleCharacter extends Group {
|
|
19
|
+
camera;
|
|
20
|
+
world;
|
|
21
|
+
options;
|
|
22
|
+
cameraBehavior;
|
|
23
|
+
physics;
|
|
24
|
+
currentAnimationRef = {};
|
|
25
|
+
//loaded asychronously
|
|
26
|
+
model;
|
|
27
|
+
updateTimeline;
|
|
28
|
+
graph = new GraphTimeline('moving');
|
|
29
|
+
abortController = new AbortController();
|
|
30
|
+
inputs;
|
|
31
|
+
lastJump = 0;
|
|
32
|
+
abortSignal = this.abortController.signal;
|
|
33
|
+
constructor(camera, world, domElement, options = {}) {
|
|
34
|
+
super();
|
|
35
|
+
this.camera = camera;
|
|
36
|
+
this.world = world;
|
|
37
|
+
this.options = options;
|
|
38
|
+
this.inputs = (options.input ?? [ScreenJoystickInput, ScreenJumpButtonInput, PointerCaptureInput, LocomotionKeyboardInput]).map((Input) => new Input(domElement));
|
|
39
|
+
applySimpleCharacterInputOptions(this.inputs, options.inputOptions);
|
|
40
|
+
// camera behavior
|
|
41
|
+
this.cameraBehavior = new CharacterCameraBehavior();
|
|
42
|
+
// physics
|
|
43
|
+
this.physics = new BvhCharacterPhysics(world);
|
|
44
|
+
// timeline graph
|
|
45
|
+
this.updateTimeline = runTimeline(this.graph.run(), this.abortController.signal);
|
|
46
|
+
//init resource loading
|
|
47
|
+
this.init(this.options).catch(console.error);
|
|
48
|
+
}
|
|
49
|
+
async init(options) {
|
|
50
|
+
if (options.model === false) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
this.model = await loadCharacterModel(...flattenCharacterModelOptions(options.model === true ? undefined : options.model));
|
|
54
|
+
this.add(this.model.scene);
|
|
55
|
+
const [jumpForwardAction, jumpUpAction] = await Promise.all([
|
|
56
|
+
loadSimpleCharacterJumpForwardAction(this, options),
|
|
57
|
+
loadSimpleCharacterJumpUpAction(this, options),
|
|
58
|
+
]);
|
|
59
|
+
await Promise.all([
|
|
60
|
+
loadSimpleCharacterJumpDownState(this, options).then((state) => this.graph.attach('jumpDown', state.timeline, state.transitionTo)),
|
|
61
|
+
loadSimpleCharacterJumpLoopState(this, options).then((state) => this.graph.attach('jumpLoop', state.timeline, state.transitionTo)),
|
|
62
|
+
,
|
|
63
|
+
loadSimpleCharacterJumpForwardState(jumpForwardAction, this, options).then((state) => this.graph.attach('jumpForward', state.timeline, state.transitionTo)),
|
|
64
|
+
,
|
|
65
|
+
loadSimpleCharacterJumpUpState(jumpUpAction, this, options).then((state) => this.graph.attach('jumpUp', state.timeline, state.transitionTo)),
|
|
66
|
+
,
|
|
67
|
+
loadSimpleCharacterJumpStartState(jumpUpAction, jumpForwardAction, this, options).then((state) => this.graph.attach('jumpStart', state.timeline, state.transitionTo)),
|
|
68
|
+
,
|
|
69
|
+
loadSimpleCharacterMovingState(this, options).then((state) => this.graph.attach('moving', state.timeline, state.transitionTo)),
|
|
70
|
+
,
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
update(delta) {
|
|
74
|
+
const jumpOptions = this.options.movement?.jump;
|
|
75
|
+
if (jumpOptions != false &&
|
|
76
|
+
this.model == null &&
|
|
77
|
+
shouldJump(this.physics, this.lastJump, jumpOptions == true ? undefined : jumpOptions?.bufferTime)) {
|
|
78
|
+
this.physics.applyVelocity(new Vector3(0, (typeof this.options.movement?.jump === 'object' ? this.options.movement?.jump.speed : undefined) ?? 8, 0));
|
|
79
|
+
this.lastJump = performance.now() / 1000;
|
|
80
|
+
}
|
|
81
|
+
if (this.model != null) {
|
|
82
|
+
updateSimpleCharacterRotation(delta, this.physics, this.camera, this.model, this.options.animation);
|
|
83
|
+
}
|
|
84
|
+
updateSimpleCharacterInputVelocity(this.camera, this.physics, this.options.movement);
|
|
85
|
+
this.updateTimeline?.(undefined, delta);
|
|
86
|
+
this.model?.mixer.update(delta);
|
|
87
|
+
if (this.model instanceof VRM) {
|
|
88
|
+
this.model.update(delta);
|
|
89
|
+
}
|
|
90
|
+
this.physics.update(this, delta, this.options.physics);
|
|
91
|
+
this.cameraBehavior.update(this.camera, this, delta, this.world.raycast.bind(this.world), this.options.cameraBehavior);
|
|
92
|
+
}
|
|
93
|
+
dispose() {
|
|
94
|
+
this.abortController.abort();
|
|
95
|
+
this.parent?.remove(this);
|
|
96
|
+
this.model?.scene.dispatchEvent({ type: 'dispose' });
|
|
97
|
+
this.inputs.forEach((input) => input.dispose());
|
|
98
|
+
this.cameraBehavior.dispose();
|
|
99
|
+
VRMUtils.deepDispose(this);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
export * from './update-input-velocity.js';
|
|
103
|
+
export * from './update-rotation.js';
|
|
104
|
+
export * from './apply-input-options.js';
|