@pmndrs/viverse 0.1.19 → 0.2.0
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/bvh-bone-map.json +24 -0
- package/dist/animation/bvh.d.ts +4 -0
- package/dist/animation/bvh.js +8 -0
- package/dist/animation/default.d.ts +1 -0
- package/dist/animation/default.js +18 -0
- package/dist/animation/fbx.d.ts +2 -2
- package/dist/animation/gltf.d.ts +2 -2
- package/dist/animation/index.d.ts +16 -17
- package/dist/animation/index.js +118 -67
- package/dist/animation/mask.d.ts +3 -0
- package/dist/animation/mask.js +3 -0
- package/dist/camera.d.ts +3 -7
- package/dist/camera.js +16 -24
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/input/index.d.ts +6 -7
- package/dist/input/index.js +5 -4
- package/dist/input/keyboard.d.ts +3 -4
- package/dist/input/keyboard.js +8 -10
- package/dist/input/pointer-capture.d.ts +3 -3
- package/dist/input/pointer-capture.js +11 -12
- package/dist/input/pointer-lock.d.ts +3 -3
- package/dist/input/pointer-lock.js +10 -10
- package/dist/input/screen-joystick.d.ts +6 -10
- package/dist/input/screen-joystick.js +29 -36
- package/dist/input/screen-jump-button.d.ts +1 -1
- package/dist/model/index.d.ts +10 -13
- package/dist/model/index.js +26 -29
- package/dist/physics/index.d.ts +2 -5
- package/dist/physics/index.js +7 -16
- package/dist/simple-character/defaults.d.ts +2 -0
- package/dist/simple-character/defaults.js +2 -0
- package/dist/simple-character/index.d.ts +101 -0
- package/dist/simple-character/index.js +109 -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 +25 -0
- package/dist/simple-character/state/jump-forward.d.ts +5 -0
- package/dist/simple-character/state/jump-forward.js +39 -0
- package/dist/simple-character/state/jump-loop.d.ts +3 -0
- package/dist/simple-character/state/jump-loop.js +23 -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 +38 -0
- package/dist/simple-character/state/movement.d.ts +3 -0
- package/dist/simple-character/state/movement.js +59 -0
- package/dist/simple-character/update-input-velocity.d.ts +5 -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 +28 -39
- package/package.json +2 -2
- package/dist/simple-character.d.ts +0 -106
- package/dist/simple-character.js +0 -342
|
@@ -0,0 +1,109 @@
|
|
|
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, InputSystem, } 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
|
+
export class SimpleCharacter extends Group {
|
|
18
|
+
camera;
|
|
19
|
+
world;
|
|
20
|
+
options;
|
|
21
|
+
cameraBehavior;
|
|
22
|
+
physics;
|
|
23
|
+
inputSystem;
|
|
24
|
+
currentAnimationRef = {};
|
|
25
|
+
//loaded asychronously
|
|
26
|
+
model;
|
|
27
|
+
updateTimeline;
|
|
28
|
+
graph = new GraphTimeline('moving');
|
|
29
|
+
abortController = new AbortController();
|
|
30
|
+
lastJump = 0;
|
|
31
|
+
constructor(camera, world, domElement, options = {}) {
|
|
32
|
+
super();
|
|
33
|
+
this.camera = camera;
|
|
34
|
+
this.world = world;
|
|
35
|
+
this.options = options;
|
|
36
|
+
this.inputSystem = new InputSystem();
|
|
37
|
+
const inputOptions = options.input ?? [
|
|
38
|
+
ScreenJoystickInput,
|
|
39
|
+
ScreenJumpButtonInput,
|
|
40
|
+
PointerCaptureInput,
|
|
41
|
+
LocomotionKeyboardInput,
|
|
42
|
+
];
|
|
43
|
+
for (let input of inputOptions) {
|
|
44
|
+
this.inputSystem.add(typeof input === 'function' ? new input(domElement) : input);
|
|
45
|
+
}
|
|
46
|
+
// camera behavior
|
|
47
|
+
this.cameraBehavior = new CharacterCameraBehavior();
|
|
48
|
+
console.log(this.graph);
|
|
49
|
+
// physics
|
|
50
|
+
this.physics = new BvhCharacterPhysics(world);
|
|
51
|
+
// timeline graph
|
|
52
|
+
this.updateTimeline = runTimeline(this.graph.run(), this.abortController.signal);
|
|
53
|
+
//init resource loading
|
|
54
|
+
this.init(this.options).catch(console.error);
|
|
55
|
+
}
|
|
56
|
+
async init(options) {
|
|
57
|
+
if (options.model === false) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
this.model = await loadCharacterModel(...flattenCharacterModelOptions(options.model === true ? undefined : options.model));
|
|
61
|
+
this.add(this.model.scene);
|
|
62
|
+
const [jumpForwardAction, jumpUpAction] = await Promise.all([
|
|
63
|
+
loadSimpleCharacterJumpForwardAction(this, options),
|
|
64
|
+
loadSimpleCharacterJumpUpAction(this, options),
|
|
65
|
+
]);
|
|
66
|
+
await Promise.all([
|
|
67
|
+
loadSimpleCharacterJumpDownState(this, options).then((state) => this.graph.attach('jumpDown', state.timeline, state.transitionTo)),
|
|
68
|
+
loadSimpleCharacterJumpLoopState(this, options).then((state) => this.graph.attach('jumpLoop', state.timeline, state.transitionTo)),
|
|
69
|
+
,
|
|
70
|
+
loadSimpleCharacterJumpForwardState(jumpForwardAction, this, options).then((state) => this.graph.attach('jumpForward', state.timeline, state.transitionTo)),
|
|
71
|
+
,
|
|
72
|
+
loadSimpleCharacterJumpUpState(jumpUpAction, this, options).then((state) => this.graph.attach('jumpUp', state.timeline, state.transitionTo)),
|
|
73
|
+
,
|
|
74
|
+
loadSimpleCharacterJumpStartState(jumpUpAction, jumpForwardAction, this, options).then((state) => this.graph.attach('jumpStart', state.timeline, state.transitionTo)),
|
|
75
|
+
,
|
|
76
|
+
loadSimpleCharacterMovingState(this, options).then((state) => this.graph.attach('moving', state.timeline, state.transitionTo)),
|
|
77
|
+
,
|
|
78
|
+
]);
|
|
79
|
+
}
|
|
80
|
+
update(delta) {
|
|
81
|
+
const jumpOptions = this.options.movement?.jump;
|
|
82
|
+
if (jumpOptions != false &&
|
|
83
|
+
this.model == null &&
|
|
84
|
+
shouldJump(this.physics, this.inputSystem, this.lastJump, jumpOptions == true ? undefined : jumpOptions?.bufferTime)) {
|
|
85
|
+
this.physics.applyVelocity(new Vector3(0, (typeof this.options.movement?.jump === 'object' ? this.options.movement?.jump.speed : undefined) ?? 8, 0));
|
|
86
|
+
this.lastJump = performance.now() / 1000;
|
|
87
|
+
}
|
|
88
|
+
if (this.model != null) {
|
|
89
|
+
updateSimpleCharacterRotation(delta, this.physics, this.camera, this.model, this.options.animation);
|
|
90
|
+
}
|
|
91
|
+
updateSimpleCharacterInputVelocity(this.camera, this.inputSystem, this.physics, this.options.movement);
|
|
92
|
+
this.updateTimeline?.(undefined, delta);
|
|
93
|
+
this.model?.mixer.update(delta);
|
|
94
|
+
if (this.model instanceof VRM) {
|
|
95
|
+
this.model.update(delta);
|
|
96
|
+
}
|
|
97
|
+
this.physics.update(this, delta, this.options.physics);
|
|
98
|
+
this.cameraBehavior.update(this.camera, this, this.inputSystem, delta, this.world.raycast.bind(this.world), this.options.cameraBehavior);
|
|
99
|
+
}
|
|
100
|
+
dispose() {
|
|
101
|
+
this.abortController.abort();
|
|
102
|
+
this.parent?.remove(this);
|
|
103
|
+
this.model?.scene.dispatchEvent({ type: 'dispose' });
|
|
104
|
+
this.inputSystem.dispose();
|
|
105
|
+
VRMUtils.deepDispose(this);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
export * from './update-input-velocity.js';
|
|
109
|
+
export * from './update-rotation.js';
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type GraphTimelineState } from '@pmndrs/timeline';
|
|
2
|
+
import type { SimpleCharacterState, SimpleCharacterOptions } from '../index.js';
|
|
3
|
+
export declare function loadSimpleCharacterJumpDownState<T>(state: SimpleCharacterState, options: SimpleCharacterOptions): Promise<GraphTimelineState<T>>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { action, timePassed } from '@pmndrs/timeline';
|
|
2
|
+
import { LoopOnce } from 'three';
|
|
3
|
+
import { flattenCharacterAnimationOptions, loadCharacterAnimation } from '../../animation/index.js';
|
|
4
|
+
import { startAnimation } from '../../utils.js';
|
|
5
|
+
import { DefaultCrossFadeDuration } from '../defaults.js';
|
|
6
|
+
export async function loadSimpleCharacterJumpDownState(state, options) {
|
|
7
|
+
const model = state.model;
|
|
8
|
+
if (model == null) {
|
|
9
|
+
throw new Error(`Unable to load animation without existing model`);
|
|
10
|
+
}
|
|
11
|
+
const jumpDown = model.mixer.clipAction(await loadCharacterAnimation(model, ...flattenCharacterAnimationOptions(options.animation?.jumpDown ?? { url: { default: 'jumpDown' } })));
|
|
12
|
+
jumpDown.loop = LoopOnce;
|
|
13
|
+
jumpDown.clampWhenFinished = true;
|
|
14
|
+
return {
|
|
15
|
+
timeline: () => action({
|
|
16
|
+
init: () => {
|
|
17
|
+
startAnimation(jumpDown, model.currentAnimations, {
|
|
18
|
+
fadeDuration: options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration,
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
until: timePassed(150, 'milliseconds'),
|
|
22
|
+
}),
|
|
23
|
+
transitionTo: { finally: 'moving' },
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type GraphTimelineState } from '@pmndrs/timeline';
|
|
2
|
+
import { AnimationAction } from 'three';
|
|
3
|
+
import { SimpleCharacterState, SimpleCharacterOptions } from '../index.js';
|
|
4
|
+
export declare function loadSimpleCharacterJumpForwardAction(state: SimpleCharacterState, options: SimpleCharacterOptions): Promise<AnimationAction>;
|
|
5
|
+
export declare function loadSimpleCharacterJumpForwardState<T>(jumpForward: AnimationAction, state: SimpleCharacterState, options: SimpleCharacterOptions): Promise<GraphTimelineState<T>>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { action, animationFinished } from '@pmndrs/timeline';
|
|
2
|
+
import { LoopOnce, Vector3 } from 'three';
|
|
3
|
+
import { flattenCharacterAnimationOptions, loadCharacterAnimation } from '../../animation/index.js';
|
|
4
|
+
import { startAnimation } from '../../utils.js';
|
|
5
|
+
import { DefaultCrossFadeDuration } from '../defaults.js';
|
|
6
|
+
export async function loadSimpleCharacterJumpForwardAction(state, options) {
|
|
7
|
+
const model = state.model;
|
|
8
|
+
if (model == null) {
|
|
9
|
+
throw new Error(`Unable to load animation without existing model`);
|
|
10
|
+
}
|
|
11
|
+
const jumpForward = model.mixer.clipAction(await loadCharacterAnimation(model, ...flattenCharacterAnimationOptions(options.animation?.jumpForward ?? {
|
|
12
|
+
url: { default: 'jumpForward' },
|
|
13
|
+
scaleTime: 0.9,
|
|
14
|
+
})));
|
|
15
|
+
jumpForward.loop = LoopOnce;
|
|
16
|
+
jumpForward.clampWhenFinished = true;
|
|
17
|
+
return jumpForward;
|
|
18
|
+
}
|
|
19
|
+
export async function loadSimpleCharacterJumpForwardState(jumpForward, state, options) {
|
|
20
|
+
const model = state.model;
|
|
21
|
+
if (model == null) {
|
|
22
|
+
throw new Error(`Unable to load animation state without existing model`);
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
timeline: () => action({
|
|
26
|
+
init: () => {
|
|
27
|
+
startAnimation(jumpForward, model.currentAnimations, {
|
|
28
|
+
fadeDuration: options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration,
|
|
29
|
+
});
|
|
30
|
+
state.lastJump = performance.now() / 1000;
|
|
31
|
+
state.physics.applyVelocity(new Vector3(0, (typeof options.movement?.jump === 'object' ? options.movement?.jump.speed : undefined) ?? 8, 0));
|
|
32
|
+
},
|
|
33
|
+
until: animationFinished(jumpForward),
|
|
34
|
+
}),
|
|
35
|
+
transitionTo: {
|
|
36
|
+
finally: () => (state.physics.isGrounded ? 'moving' : 'jumpLoop'),
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type GraphTimelineState } from '@pmndrs/timeline';
|
|
2
|
+
import type { SimpleCharacterOptions, SimpleCharacterState } from '../index.js';
|
|
3
|
+
export declare function loadSimpleCharacterJumpLoopState<T>(state: SimpleCharacterState, options: SimpleCharacterOptions): Promise<GraphTimelineState<T>>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { action } from '@pmndrs/timeline';
|
|
2
|
+
import { flattenCharacterAnimationOptions, loadCharacterAnimation } from '../../animation/index.js';
|
|
3
|
+
import { startAnimation } from '../../utils.js';
|
|
4
|
+
import { DefaultCrossFadeDuration } from '../defaults.js';
|
|
5
|
+
export async function loadSimpleCharacterJumpLoopState(state, options) {
|
|
6
|
+
const model = state.model;
|
|
7
|
+
if (model == null) {
|
|
8
|
+
throw new Error(`Unable to load animation without existing model`);
|
|
9
|
+
}
|
|
10
|
+
const jumpLoop = model.mixer.clipAction(await loadCharacterAnimation(model, ...flattenCharacterAnimationOptions(options.animation?.jumpLoop ?? { url: { default: 'jumpLoop' } })));
|
|
11
|
+
return {
|
|
12
|
+
timeline: () => action({
|
|
13
|
+
init: () => {
|
|
14
|
+
startAnimation(jumpLoop, model.currentAnimations, {
|
|
15
|
+
fadeDuration: options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration,
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
transitionTo: {
|
|
20
|
+
jumpDown: { whenUpdate: () => state.physics.isGrounded },
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { GraphTimelineState } from '@pmndrs/timeline';
|
|
2
|
+
import { AnimationAction } from 'three';
|
|
3
|
+
import type { SimpleCharacterOptions, SimpleCharacterState } from '../index.js';
|
|
4
|
+
export declare function loadSimpleCharacterJumpStartState<T>(jumpUp: AnimationAction, jumpForward: AnimationAction, state: SimpleCharacterState, options: SimpleCharacterOptions): Promise<GraphTimelineState<T>>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { action, timePassed } from '@pmndrs/timeline';
|
|
2
|
+
import { RunField } from '../../input/index.js';
|
|
3
|
+
import { startAnimation } from '../../utils.js';
|
|
4
|
+
import { DefaultCrossFadeDuration, DefaultJumDelay } from '../defaults.js';
|
|
5
|
+
export async function loadSimpleCharacterJumpStartState(jumpUp, jumpForward, state, options) {
|
|
6
|
+
const model = state.model;
|
|
7
|
+
if (model == null) {
|
|
8
|
+
throw new Error(`Unable to load animation state without existing model`);
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
timeline: () => action({
|
|
12
|
+
init() {
|
|
13
|
+
startAnimation(jumpUp, model.currentAnimations, {
|
|
14
|
+
fadeDuration: options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration,
|
|
15
|
+
paused: true,
|
|
16
|
+
});
|
|
17
|
+
startAnimation(jumpForward, model.currentAnimations, {
|
|
18
|
+
fadeDuration: options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration,
|
|
19
|
+
paused: true,
|
|
20
|
+
});
|
|
21
|
+
},
|
|
22
|
+
update: () => void state.physics.inputVelocity.multiplyScalar(0.3),
|
|
23
|
+
until: timePassed((typeof options.movement?.jump === 'object' ? options.movement?.jump.delay : undefined) ?? DefaultJumDelay, 'seconds'),
|
|
24
|
+
}),
|
|
25
|
+
transitionTo: {
|
|
26
|
+
jumpDown: { whenUpdate: () => !state.physics.isGrounded },
|
|
27
|
+
finally: () => (state.inputSystem.get(RunField) ? 'jumpForward' : 'jumpUp'),
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { GraphTimelineState } from '@pmndrs/timeline';
|
|
2
|
+
import { AnimationAction } from 'three';
|
|
3
|
+
import { SimpleCharacterState, SimpleCharacterOptions } from '../index.js';
|
|
4
|
+
export declare function loadSimpleCharacterJumpUpAction(state: SimpleCharacterState, options: SimpleCharacterOptions): Promise<AnimationAction>;
|
|
5
|
+
export declare function loadSimpleCharacterJumpUpState<T>(jumpUp: AnimationAction, state: SimpleCharacterState, options: SimpleCharacterOptions): Promise<GraphTimelineState<T>>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { action, animationFinished } from '@pmndrs/timeline';
|
|
2
|
+
import { LoopOnce, Vector3 } from 'three';
|
|
3
|
+
import { flattenCharacterAnimationOptions, loadCharacterAnimation } from '../../animation/index.js';
|
|
4
|
+
import { startAnimation } from '../../utils.js';
|
|
5
|
+
export async function loadSimpleCharacterJumpUpAction(state, options) {
|
|
6
|
+
const model = state.model;
|
|
7
|
+
if (model == null) {
|
|
8
|
+
throw new Error(`Unable to load animation without existing model`);
|
|
9
|
+
}
|
|
10
|
+
const jumpUp = model.mixer.clipAction(await loadCharacterAnimation(model, ...flattenCharacterAnimationOptions(options.animation?.jumpUp ?? {
|
|
11
|
+
url: { default: 'jumpUp' },
|
|
12
|
+
})));
|
|
13
|
+
jumpUp.loop = LoopOnce;
|
|
14
|
+
jumpUp.clampWhenFinished = true;
|
|
15
|
+
return jumpUp;
|
|
16
|
+
}
|
|
17
|
+
export async function loadSimpleCharacterJumpUpState(jumpUp, state, options) {
|
|
18
|
+
const model = state.model;
|
|
19
|
+
if (model == null) {
|
|
20
|
+
throw new Error(`Unable to load animation state without existing model`);
|
|
21
|
+
}
|
|
22
|
+
return {
|
|
23
|
+
timeline: () => action({
|
|
24
|
+
init: () => {
|
|
25
|
+
startAnimation(jumpUp, model.currentAnimations, { fadeDuration: 0.1 });
|
|
26
|
+
state.lastJump = performance.now() / 1000;
|
|
27
|
+
state.physics.applyVelocity(new Vector3(0, (typeof options.movement?.jump === 'object' ? options.movement?.jump.speed : undefined) ?? 8, 0));
|
|
28
|
+
},
|
|
29
|
+
until: animationFinished(jumpUp),
|
|
30
|
+
}),
|
|
31
|
+
transitionTo: {
|
|
32
|
+
jumpDown: {
|
|
33
|
+
whenUpdate: (_, _clock, actionTime) => actionTime > 0.3 && state.physics.isGrounded,
|
|
34
|
+
},
|
|
35
|
+
finally: 'jumpLoop',
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type GraphTimelineState } from '@pmndrs/timeline';
|
|
2
|
+
import type { SimpleCharacterOptions, SimpleCharacterState } from '../index.js';
|
|
3
|
+
export declare function loadSimpleCharacterMovingState<T>(state: SimpleCharacterState, options: SimpleCharacterOptions): Promise<GraphTimelineState<T>>;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { action } from '@pmndrs/timeline';
|
|
2
|
+
import { flattenCharacterAnimationOptions, loadCharacterAnimation } from '../../animation/index.js';
|
|
3
|
+
import { RunField } from '../../input/index.js';
|
|
4
|
+
import { shouldJump, startAnimation } from '../../utils.js';
|
|
5
|
+
import { DefaultCrossFadeDuration } from '../defaults.js';
|
|
6
|
+
export async function loadSimpleCharacterMovingState(state, options) {
|
|
7
|
+
const model = state.model;
|
|
8
|
+
if (model == null) {
|
|
9
|
+
throw new Error(`Unable to load animation without existing model`);
|
|
10
|
+
}
|
|
11
|
+
const idle = model.mixer.clipAction(await loadCharacterAnimation(model, ...flattenCharacterAnimationOptions(options.animation?.idle ?? {
|
|
12
|
+
url: { default: 'idle' },
|
|
13
|
+
})));
|
|
14
|
+
const run = model.mixer.clipAction(await loadCharacterAnimation(model, ...flattenCharacterAnimationOptions({
|
|
15
|
+
url: { default: 'run' },
|
|
16
|
+
scaleTime: 0.8,
|
|
17
|
+
})));
|
|
18
|
+
const walk = model.mixer.clipAction(await loadCharacterAnimation(model, ...flattenCharacterAnimationOptions(options.animation?.walk ?? {
|
|
19
|
+
scaleTime: 0.5,
|
|
20
|
+
url: { default: 'walk' },
|
|
21
|
+
})));
|
|
22
|
+
return {
|
|
23
|
+
timeline: () => {
|
|
24
|
+
let currentAnimation;
|
|
25
|
+
return action({
|
|
26
|
+
update: () => {
|
|
27
|
+
let nextAnimation;
|
|
28
|
+
if (state.physics.inputVelocity.lengthSq() === 0) {
|
|
29
|
+
nextAnimation = idle;
|
|
30
|
+
}
|
|
31
|
+
else if (state.inputSystem.get(RunField) && options.movement?.run != false) {
|
|
32
|
+
nextAnimation = run;
|
|
33
|
+
}
|
|
34
|
+
else if (options.movement?.walk != false) {
|
|
35
|
+
nextAnimation = walk;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
nextAnimation = idle;
|
|
39
|
+
}
|
|
40
|
+
if (nextAnimation === currentAnimation) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
currentAnimation?.fadeOut(options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration);
|
|
44
|
+
startAnimation(nextAnimation, model.currentAnimations, {
|
|
45
|
+
fadeDuration: options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration,
|
|
46
|
+
});
|
|
47
|
+
currentAnimation = nextAnimation;
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
},
|
|
51
|
+
transitionTo: {
|
|
52
|
+
jumpStart: {
|
|
53
|
+
whenUpdate: () => options.movement?.jump !== false &&
|
|
54
|
+
shouldJump(state.physics, state.inputSystem, state.lastJump, options.movement?.jump === true ? undefined : options.movement?.jump?.bufferTime),
|
|
55
|
+
},
|
|
56
|
+
jumpLoop: { whenUpdate: () => !state.physics.isGrounded },
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Object3D } from 'three';
|
|
2
|
+
import { InputSystem } from '../input/index.js';
|
|
3
|
+
import type { SimpleCharacterMovementOptions } from './index.js';
|
|
4
|
+
import type { BvhCharacterPhysics } from '../physics/index.js';
|
|
5
|
+
export declare function updateSimpleCharacterInputVelocity(camera: Object3D, inputSystem: InputSystem, physics: BvhCharacterPhysics, options?: SimpleCharacterMovementOptions): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Euler, Quaternion } from 'three';
|
|
2
|
+
import { RunField, MoveLeftField, MoveRightField, MoveForwardField, MoveBackwardField, } from '../input/index.js';
|
|
3
|
+
const cameraEuler = new Euler();
|
|
4
|
+
const cameraRotation = new Quaternion();
|
|
5
|
+
export function updateSimpleCharacterInputVelocity(camera, inputSystem, physics, options) {
|
|
6
|
+
cameraEuler.setFromQuaternion(camera.getWorldQuaternion(cameraRotation), 'YXZ');
|
|
7
|
+
cameraEuler.x = 0;
|
|
8
|
+
cameraEuler.z = 0;
|
|
9
|
+
let inputSpeed = 0;
|
|
10
|
+
let runOptions = options?.run ?? true;
|
|
11
|
+
if (inputSystem.get(RunField) && runOptions !== false) {
|
|
12
|
+
runOptions = runOptions === true ? {} : runOptions;
|
|
13
|
+
inputSpeed = runOptions.speed ?? 6;
|
|
14
|
+
}
|
|
15
|
+
let walkOptions = options?.walk ?? true;
|
|
16
|
+
if (inputSpeed === 0 && walkOptions !== false) {
|
|
17
|
+
walkOptions = walkOptions === true ? {} : walkOptions;
|
|
18
|
+
inputSpeed = walkOptions.speed ?? 3;
|
|
19
|
+
}
|
|
20
|
+
physics.inputVelocity
|
|
21
|
+
.set(-inputSystem.get(MoveLeftField) + inputSystem.get(MoveRightField), 0, -inputSystem.get(MoveForwardField) + inputSystem.get(MoveBackwardField))
|
|
22
|
+
.normalize()
|
|
23
|
+
.applyEuler(cameraEuler)
|
|
24
|
+
.multiplyScalar(inputSpeed);
|
|
25
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { Object3D } from 'three';
|
|
2
|
+
import { BvhCharacterPhysics } from '../physics/index.js';
|
|
3
|
+
import type { SimpleCharacterAnimationOptions } from './index.js';
|
|
4
|
+
export declare function updateSimpleCharacterRotation(delta: number, physics: BvhCharacterPhysics, camera: Object3D, model: {
|
|
5
|
+
scene: Object3D;
|
|
6
|
+
}, options?: SimpleCharacterAnimationOptions): true | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Euler, Quaternion, Vector3 } from 'three';
|
|
2
|
+
const NegZAxis = new Vector3(0, 0, -1);
|
|
3
|
+
const _2MathPI = 2 * Math.PI;
|
|
4
|
+
const characterTargetEuler = new Euler();
|
|
5
|
+
const goalTargetEuler = new Euler();
|
|
6
|
+
const inputDirection = new Vector3();
|
|
7
|
+
const quaternion = new Quaternion();
|
|
8
|
+
export function updateSimpleCharacterRotation(delta, physics, camera, model, options) {
|
|
9
|
+
// Character yaw rotation logic
|
|
10
|
+
const basedOn = options?.yawRotationBasdOn ?? 'movement';
|
|
11
|
+
// compute goalTargetEuler
|
|
12
|
+
if (basedOn === 'camera') {
|
|
13
|
+
goalTargetEuler.setFromQuaternion(camera.getWorldQuaternion(quaternion), 'YXZ');
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
//don't rotate if not moving
|
|
17
|
+
if (physics.inputVelocity.lengthSq() === 0) {
|
|
18
|
+
// run forever
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
inputDirection.copy(physics.inputVelocity).normalize();
|
|
22
|
+
quaternion.setFromUnitVectors(NegZAxis, inputDirection);
|
|
23
|
+
goalTargetEuler.setFromQuaternion(quaternion, 'YXZ');
|
|
24
|
+
}
|
|
25
|
+
// compute currentTargetEuler
|
|
26
|
+
model.scene.getWorldQuaternion(quaternion);
|
|
27
|
+
characterTargetEuler.setFromQuaternion(quaternion, 'YXZ');
|
|
28
|
+
// apply delta yaw rotation
|
|
29
|
+
let deltaYaw = (goalTargetEuler.y - characterTargetEuler.y + _2MathPI) % _2MathPI;
|
|
30
|
+
if (deltaYaw > Math.PI) {
|
|
31
|
+
deltaYaw = deltaYaw - _2MathPI;
|
|
32
|
+
}
|
|
33
|
+
const absDeltaYaw = Math.abs(deltaYaw);
|
|
34
|
+
if (absDeltaYaw < 0.001) {
|
|
35
|
+
// run forever
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
const yawRotationDirection = deltaYaw / absDeltaYaw;
|
|
39
|
+
model.scene.rotation.y += Math.min((options?.maxYawRotationSpeed ?? 10) * delta, absDeltaYaw) * yawRotationDirection;
|
|
40
|
+
}
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
import { type InputSystem } from './input/index.js';
|
|
2
|
+
import type { CharacterAnimationMask } from './animation/index.js';
|
|
3
|
+
import type { CharacterModel } from './model/index.js';
|
|
4
|
+
import type { BvhCharacterPhysics } from './physics/index.js';
|
|
5
|
+
import type { AnimationAction } from 'three';
|
|
6
6
|
export declare function getIsMobileMediaQuery(): MediaQueryList | undefined;
|
|
7
7
|
export declare function isMobile(): boolean;
|
|
8
|
+
export type StartAnimationOptions = {
|
|
9
|
+
fadeDuration?: number;
|
|
10
|
+
paused?: boolean;
|
|
11
|
+
mask?: CharacterAnimationMask;
|
|
12
|
+
};
|
|
13
|
+
export declare function startAnimation(animation: AnimationAction, currentAnimations: CharacterModel['currentAnimations'], { fadeDuration, paused, mask }: StartAnimationOptions): void;
|
|
14
|
+
export declare function shouldJump(physics: BvhCharacterPhysics, inputSystem: InputSystem, lastJump: number, bufferTime?: number): boolean;
|
package/dist/utils.js
CHANGED
|
@@ -1,42 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
if (a.length !== b.length)
|
|
3
|
-
return false;
|
|
4
|
-
for (let i = 0; i < a.length; i++) {
|
|
5
|
-
if (a[i] !== b[i])
|
|
6
|
-
return false;
|
|
7
|
-
}
|
|
8
|
-
return true;
|
|
9
|
-
}
|
|
10
|
-
const cacheMap = new Map();
|
|
11
|
-
export function cached(fn, dependencies) {
|
|
12
|
-
let cache = cacheMap.get(fn);
|
|
13
|
-
if (cache == null) {
|
|
14
|
-
cacheMap.set(fn, (cache = []));
|
|
15
|
-
}
|
|
16
|
-
const entry = cache.find(({ deps }) => shallowEqual(deps, dependencies));
|
|
17
|
-
if (entry != null) {
|
|
18
|
-
return entry.result;
|
|
19
|
-
}
|
|
20
|
-
const result = fn(...dependencies);
|
|
21
|
-
cache.push({ deps: dependencies, result });
|
|
22
|
-
return result;
|
|
23
|
-
}
|
|
24
|
-
export function clearCache(fn, dependencies) {
|
|
25
|
-
const cache = cacheMap.get(fn);
|
|
26
|
-
if (cache == null) {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
const index = cache.findIndex(({ deps }) => shallowEqual(deps, dependencies));
|
|
30
|
-
if (index === -1) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
cache.splice(index, 1);
|
|
34
|
-
}
|
|
35
|
-
export function extractProxy(value, key) {
|
|
36
|
-
return new Proxy({}, {
|
|
37
|
-
get: (_, p) => value[key]?.[p],
|
|
38
|
-
});
|
|
39
|
-
}
|
|
1
|
+
import { LastTimeJumpPressedField } from './input/index.js';
|
|
40
2
|
export function getIsMobileMediaQuery() {
|
|
41
3
|
if (typeof window === 'undefined' || typeof window.matchMedia !== 'function') {
|
|
42
4
|
return undefined;
|
|
@@ -46,3 +8,30 @@ export function getIsMobileMediaQuery() {
|
|
|
46
8
|
export function isMobile() {
|
|
47
9
|
return getIsMobileMediaQuery()?.matches ?? false;
|
|
48
10
|
}
|
|
11
|
+
export function startAnimation(animation, currentAnimations, { fadeDuration = 0.1, paused = false, mask }) {
|
|
12
|
+
animation.reset();
|
|
13
|
+
animation.play();
|
|
14
|
+
animation.paused = paused;
|
|
15
|
+
const currentAnimation = currentAnimations.get(mask);
|
|
16
|
+
if (currentAnimation != null) {
|
|
17
|
+
animation.crossFadeFrom(currentAnimation, fadeDuration);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
animation.fadeIn(fadeDuration);
|
|
21
|
+
}
|
|
22
|
+
currentAnimations.set(mask, animation);
|
|
23
|
+
}
|
|
24
|
+
export function shouldJump(physics, inputSystem, lastJump, bufferTime = 0.1) {
|
|
25
|
+
if (!physics.isGrounded) {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
const lastTimePressed = inputSystem.get(LastTimeJumpPressedField);
|
|
29
|
+
if (lastTimePressed == null || lastJump > lastTimePressed) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
//last jump must be more then 0.3 second ago, if not, we dont jump, this is to give the character time to get off the ground
|
|
33
|
+
if (lastJump > performance.now() / 1000 - 0.3) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
return performance.now() / 1000 - lastTimePressed < bufferTime;
|
|
37
|
+
}
|
package/package.json
CHANGED
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
"peerDependencies": {
|
|
22
22
|
"three": "*"
|
|
23
23
|
},
|
|
24
|
-
"version": "0.
|
|
24
|
+
"version": "0.2.0",
|
|
25
25
|
"type": "module",
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@pixiv/three-vrm": "^3.4.2",
|
|
28
28
|
"@pixiv/three-vrm-animation": "^3.4.2",
|
|
29
|
-
"@pmndrs/timeline": "^0.
|
|
29
|
+
"@pmndrs/timeline": "^0.3.5",
|
|
30
30
|
"@viverse/sdk": "1.2.10-alpha.0",
|
|
31
31
|
"three-mesh-bvh": "^0.9.1"
|
|
32
32
|
},
|