@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
|
@@ -1,18 +1,25 @@
|
|
|
1
|
-
export
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
export const IdleAnimationUrl = Symbol('idle-animation-url');
|
|
2
|
+
export const JumpUpAnimationUrl = Symbol('jump-up-animation-url');
|
|
3
|
+
export const JumpDownAnimationUrl = Symbol('jump-down-animation-url');
|
|
4
|
+
export const JumpForwardAnimationUrl = Symbol('jump-forward-animation-url');
|
|
5
|
+
export const JumpLoopAnimationUrl = Symbol('jump-loop-animation-url');
|
|
6
|
+
export const RunAnimationUrl = Symbol('run-animation-url');
|
|
7
|
+
export const WalkAnimationUrl = Symbol('walk-animation-url');
|
|
8
|
+
export async function resolveDefaultCharacterAnimationUrl(url) {
|
|
9
|
+
switch (url) {
|
|
10
|
+
case IdleAnimationUrl:
|
|
4
11
|
return (await import('../assets/idle.js')).url;
|
|
5
|
-
case
|
|
12
|
+
case JumpDownAnimationUrl:
|
|
6
13
|
return (await import('../assets/jump-down.js')).url;
|
|
7
|
-
case
|
|
14
|
+
case JumpForwardAnimationUrl:
|
|
8
15
|
return (await import('../assets/jump-forward.js')).url;
|
|
9
|
-
case
|
|
16
|
+
case JumpLoopAnimationUrl:
|
|
10
17
|
return (await import('../assets/jump-loop.js')).url;
|
|
11
|
-
case
|
|
18
|
+
case JumpUpAnimationUrl:
|
|
12
19
|
return (await import('../assets/jump-up.js')).url;
|
|
13
|
-
case
|
|
20
|
+
case RunAnimationUrl:
|
|
14
21
|
return (await import('../assets/run.js')).url;
|
|
15
|
-
case
|
|
22
|
+
case WalkAnimationUrl:
|
|
16
23
|
return (await import('../assets/walk.js')).url;
|
|
17
24
|
}
|
|
18
25
|
}
|
|
@@ -1,17 +1,14 @@
|
|
|
1
1
|
import { VRMHumanBoneName } from '@pixiv/three-vrm';
|
|
2
2
|
import { AnimationClip, Object3D } from 'three';
|
|
3
|
-
import {
|
|
4
|
-
import type
|
|
5
|
-
|
|
6
|
-
export
|
|
7
|
-
export * from './fbx.js';
|
|
8
|
-
export * from './vrma.js';
|
|
3
|
+
import { DefaultUrl } from './default.js';
|
|
4
|
+
import { type CharacterAnimationMask } from './mask.js';
|
|
5
|
+
import { type CharacterModel } from '../model/index.js';
|
|
6
|
+
export declare function fixModelAnimationClip(model: CharacterModel, clip: AnimationClip, clipScene: Object3D | undefined, removeXZMovement: boolean): void;
|
|
9
7
|
export * from './utils.js';
|
|
10
|
-
export
|
|
8
|
+
export * from './default.js';
|
|
9
|
+
export * from './mask.js';
|
|
11
10
|
export type CharacterAnimationOptions = {
|
|
12
|
-
url: string |
|
|
13
|
-
default: Parameters<typeof loadDefaultCharacterAnimationUrl>[0];
|
|
14
|
-
};
|
|
11
|
+
url: string | DefaultUrl;
|
|
15
12
|
type?: 'mixamo' | 'gltf' | 'vrma' | 'fbx' | 'bvh';
|
|
16
13
|
removeXZMovement?: boolean;
|
|
17
14
|
trimTime?: {
|
|
@@ -24,8 +21,6 @@ export type CharacterAnimationOptions = {
|
|
|
24
21
|
};
|
|
25
22
|
export type Tail<T extends any[]> = T extends [any, ...infer Rest] ? Rest : never;
|
|
26
23
|
export declare function flattenCharacterAnimationOptions(options: Exclude<CharacterAnimationOptions, false>): Tail<Parameters<typeof loadCharacterAnimation>>;
|
|
27
|
-
export declare function loadCharacterAnimation(model: CharacterModel, url: string |
|
|
28
|
-
default: Parameters<typeof loadDefaultCharacterAnimationUrl>[0];
|
|
29
|
-
}, type?: CharacterAnimationOptions['type'], removeXZMovement?: boolean, trimStartTime?: number | undefined, trimEndTime?: number | undefined, boneMap?: Record<string, VRMHumanBoneName> | undefined, scaleTime?: number | undefined, mask?: CharacterAnimationMask): Promise<AnimationClip>;
|
|
24
|
+
export declare function loadCharacterAnimation(model: CharacterModel, url: string | DefaultUrl, type?: CharacterAnimationOptions['type'], removeXZMovement?: boolean, trimStartTime?: number | undefined, trimEndTime?: number | undefined, boneMap?: Record<string, VRMHumanBoneName> | undefined, scaleTime?: number | undefined, mask?: CharacterAnimationMask): Promise<AnimationClip>;
|
|
30
25
|
export declare const mixamoBoneMap: Record<string, VRMHumanBoneName>;
|
|
31
26
|
export declare const bvhBoneMap: Record<string, VRMHumanBoneName>;
|
package/dist/animation/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { VRM } from '@pixiv/three-vrm';
|
|
2
2
|
import { Euler, Quaternion, QuaternionKeyframeTrack, Vector3, VectorKeyframeTrack, } from 'three';
|
|
3
|
+
import { BVHLoader } from 'three/examples/jsm/loaders/BVHLoader.js';
|
|
4
|
+
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
|
|
5
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
6
|
+
import { applyBoneMap } from './bone-map.js';
|
|
3
7
|
import _bvhBoneMap from './bvh-bone-map.json';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { loadVrmModelFbxAnimations } from './fbx.js';
|
|
7
|
-
import { loadVrmModelGltfAnimations } from './gltf.js';
|
|
8
|
+
import { resolveDefaultCharacterAnimationUrl } from './default.js';
|
|
9
|
+
import { applyMask } from './mask.js';
|
|
8
10
|
import _mixamoBoneMap from './mixamo-bone-map.json';
|
|
9
11
|
import { scaleAnimationClipTime, trimAnimationClip } from './utils.js';
|
|
10
|
-
import {
|
|
12
|
+
import { vrmaLoader } from '../model/index.js';
|
|
11
13
|
//helper variables for the quaternion retargeting
|
|
12
14
|
const baseThisLocalRestRotation_inverse = new Quaternion();
|
|
13
15
|
const baseThisLocalCurrentRotation = new Quaternion();
|
|
@@ -21,11 +23,7 @@ const targetThisLocalCurrentRotation = new Quaternion();
|
|
|
21
23
|
const position = new Vector3();
|
|
22
24
|
const nonVrmRotationOffset = new Quaternion().setFromEuler(new Euler(0, Math.PI, 0));
|
|
23
25
|
//TODO: currently assumes the model is not yet transformed - loaded for the first time
|
|
24
|
-
export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement
|
|
25
|
-
const hipsClipBoneName = boneMap == null ? 'hips' : Object.entries(boneMap).find(([, vrmBoneName]) => vrmBoneName === 'hips')?.[0];
|
|
26
|
-
if (hipsClipBoneName == null) {
|
|
27
|
-
throw new Error('Failed to determine hips bone name for VRM animation. Please check the bone map or animation file.');
|
|
28
|
-
}
|
|
26
|
+
export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement) {
|
|
29
27
|
let restRoot;
|
|
30
28
|
let restRootParent;
|
|
31
29
|
if (!(model instanceof VRM)) {
|
|
@@ -39,24 +37,22 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
39
37
|
let positionScale = 1;
|
|
40
38
|
let clipSceneHips;
|
|
41
39
|
if (clipScene != null) {
|
|
42
|
-
clipSceneHips = clipScene.getObjectByName(
|
|
40
|
+
clipSceneHips = clipScene.getObjectByName('hips');
|
|
43
41
|
clipSceneHips?.parent?.updateMatrixWorld();
|
|
44
42
|
const vrmHipsPosition = model instanceof VRM
|
|
45
43
|
? model.humanoid.normalizedRestPose.hips?.position
|
|
46
44
|
: model.scene.getObjectByName('rest_hips')?.getWorldPosition(new Vector3()).toArray();
|
|
47
|
-
if (clipSceneHips
|
|
48
|
-
|
|
45
|
+
if (clipSceneHips != null && vrmHipsPosition != null) {
|
|
46
|
+
// Adjust with reference to hips height.
|
|
47
|
+
const motionHipsHeight = clipSceneHips.getWorldPosition(position).y;
|
|
48
|
+
const [_, vrmHipsHeight] = vrmHipsPosition;
|
|
49
|
+
positionScale = vrmHipsHeight / motionHipsHeight;
|
|
49
50
|
}
|
|
50
|
-
// Adjust with reference to hips height.
|
|
51
|
-
const motionHipsHeight = clipSceneHips.getWorldPosition(position).y;
|
|
52
|
-
const [_, vrmHipsHeight] = vrmHipsPosition;
|
|
53
|
-
positionScale = vrmHipsHeight / motionHipsHeight;
|
|
54
51
|
}
|
|
55
52
|
for (const track of clip.tracks) {
|
|
56
53
|
// Convert each tracks for VRM use, and push to `tracks`
|
|
57
54
|
const [clipBoneName, propertyName] = track.name.split('.');
|
|
58
|
-
const
|
|
59
|
-
const targetNormalizedBoneName = model instanceof VRM ? model.humanoid.getNormalizedBoneNode(vrmBoneName)?.name : vrmBoneName;
|
|
55
|
+
const targetNormalizedBoneName = model instanceof VRM ? model.humanoid.getNormalizedBoneNode(clipBoneName)?.name : clipBoneName;
|
|
60
56
|
if (targetNormalizedBoneName == null) {
|
|
61
57
|
continue;
|
|
62
58
|
}
|
|
@@ -69,7 +65,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
69
65
|
targetParentWorldBoneTransform = { rotation: [0, 0, 0, 1] };
|
|
70
66
|
}
|
|
71
67
|
else {
|
|
72
|
-
const targetBone = model.scene.getObjectByName(`rest_${
|
|
68
|
+
const targetBone = model.scene.getObjectByName(`rest_${clipBoneName}`);
|
|
73
69
|
if (targetBone != null) {
|
|
74
70
|
targetLocalBoneTransform = { rotation: targetBone.quaternion.toArray() };
|
|
75
71
|
}
|
|
@@ -123,7 +119,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
123
119
|
targetThisLocalCurrentRotation.x *= -1;
|
|
124
120
|
targetThisLocalCurrentRotation.z *= -1;
|
|
125
121
|
}
|
|
126
|
-
if (!(model instanceof VRM) &&
|
|
122
|
+
if (!(model instanceof VRM) && clipBoneName === 'hips') {
|
|
127
123
|
targetThisLocalCurrentRotation.premultiply(nonVrmRotationOffset);
|
|
128
124
|
}
|
|
129
125
|
targetThisLocalCurrentRotation.toArray(track.values, i);
|
|
@@ -131,7 +127,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
131
127
|
track.name = trackName;
|
|
132
128
|
}
|
|
133
129
|
else if (track instanceof VectorKeyframeTrack) {
|
|
134
|
-
if (
|
|
130
|
+
if (clipBoneName != 'hips' && clipBoneName != 'root') {
|
|
135
131
|
continue;
|
|
136
132
|
}
|
|
137
133
|
if (propertyName != 'position') {
|
|
@@ -140,7 +136,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
140
136
|
for (let i = 0; i < track.values.length; i += 3) {
|
|
141
137
|
position.fromArray(track.values, i);
|
|
142
138
|
if (clipSceneHips?.parent != null) {
|
|
143
|
-
if (
|
|
139
|
+
if (clipBoneName === 'hips') {
|
|
144
140
|
position.applyMatrix4(clipSceneHips.parent.matrixWorld);
|
|
145
141
|
}
|
|
146
142
|
else {
|
|
@@ -148,7 +144,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
148
144
|
}
|
|
149
145
|
}
|
|
150
146
|
position.multiplyScalar(positionScale);
|
|
151
|
-
if (!(model instanceof VRM) &&
|
|
147
|
+
if (!(model instanceof VRM) && clipBoneName === 'hips') {
|
|
152
148
|
position.applyQuaternion(nonVrmRotationOffset);
|
|
153
149
|
}
|
|
154
150
|
if (model instanceof VRM) {
|
|
@@ -157,7 +153,7 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
157
153
|
position.y *= -1;
|
|
158
154
|
}
|
|
159
155
|
}
|
|
160
|
-
if (
|
|
156
|
+
if (clipBoneName === 'hips' && removeXZMovement) {
|
|
161
157
|
position.x = 0;
|
|
162
158
|
position.z = 0;
|
|
163
159
|
}
|
|
@@ -170,10 +166,9 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
170
166
|
restRoot.parent = restRootParent;
|
|
171
167
|
}
|
|
172
168
|
}
|
|
173
|
-
export * from './gltf.js';
|
|
174
|
-
export * from './fbx.js';
|
|
175
|
-
export * from './vrma.js';
|
|
176
169
|
export * from './utils.js';
|
|
170
|
+
export * from './default.js';
|
|
171
|
+
export * from './mask.js';
|
|
177
172
|
export function flattenCharacterAnimationOptions(options) {
|
|
178
173
|
return [
|
|
179
174
|
options.url,
|
|
@@ -186,12 +181,17 @@ export function flattenCharacterAnimationOptions(options) {
|
|
|
186
181
|
options.mask,
|
|
187
182
|
];
|
|
188
183
|
}
|
|
184
|
+
const gltfLoader = new GLTFLoader();
|
|
185
|
+
const fbxLoader = new FBXLoader();
|
|
186
|
+
const bvhLoader = new BVHLoader();
|
|
189
187
|
export async function loadCharacterAnimation(model, url, type, removeXZMovement = false, trimStartTime, trimEndTime, boneMap, scaleTime, mask) {
|
|
190
|
-
if (typeof url === '
|
|
191
|
-
url = await
|
|
188
|
+
if (typeof url === 'symbol') {
|
|
189
|
+
url = await resolveDefaultCharacterAnimationUrl(url);
|
|
192
190
|
type = 'gltf';
|
|
193
191
|
}
|
|
194
192
|
let clips;
|
|
193
|
+
let clipScene;
|
|
194
|
+
let defaultBoneMap;
|
|
195
195
|
if (type == null) {
|
|
196
196
|
const lowerCaseUrl = url.toLocaleLowerCase();
|
|
197
197
|
if (lowerCaseUrl.endsWith('.glb') || lowerCaseUrl.endsWith('.gltf')) {
|
|
@@ -211,29 +211,51 @@ export async function loadCharacterAnimation(model, url, type, removeXZMovement
|
|
|
211
211
|
}
|
|
212
212
|
}
|
|
213
213
|
switch (type) {
|
|
214
|
-
case 'gltf':
|
|
215
|
-
|
|
214
|
+
case 'gltf': {
|
|
215
|
+
const { animations, scene } = await gltfLoader.loadAsync(url);
|
|
216
|
+
clips = animations;
|
|
217
|
+
clipScene = scene;
|
|
216
218
|
break;
|
|
217
|
-
|
|
218
|
-
|
|
219
|
+
}
|
|
220
|
+
case 'fbx': {
|
|
221
|
+
const scene = await fbxLoader.loadAsync(url);
|
|
222
|
+
clips = scene.animations;
|
|
223
|
+
clipScene = scene;
|
|
219
224
|
break;
|
|
220
|
-
|
|
221
|
-
|
|
225
|
+
}
|
|
226
|
+
case 'bvh': {
|
|
227
|
+
const { clip, skeleton } = await bvhLoader.loadAsync(url);
|
|
228
|
+
clips = [clip];
|
|
229
|
+
boneMap ??= bvhBoneMap;
|
|
222
230
|
break;
|
|
223
|
-
|
|
224
|
-
|
|
231
|
+
}
|
|
232
|
+
case 'mixamo': {
|
|
233
|
+
const scene = await fbxLoader.loadAsync(url);
|
|
234
|
+
clips = scene.animations;
|
|
235
|
+
clipScene = scene;
|
|
236
|
+
boneMap ??= mixamoBoneMap;
|
|
225
237
|
break;
|
|
238
|
+
}
|
|
226
239
|
case 'vrma':
|
|
227
240
|
if (!(model instanceof VRM)) {
|
|
228
241
|
throw new Error(`Model must be an instance of VRM to load VRMA animations`);
|
|
229
242
|
}
|
|
230
|
-
clips = await
|
|
243
|
+
clips = (await vrmaLoader.loadAsync(url)).userData.vrmAnimations;
|
|
231
244
|
break;
|
|
232
245
|
}
|
|
233
246
|
if (clips.length != 1) {
|
|
234
247
|
throw new Error(`Expected exactly one animation clip, but got ${clips.length} for url ${url}`);
|
|
235
248
|
}
|
|
236
249
|
const [clip] = clips;
|
|
250
|
+
if (boneMap != null) {
|
|
251
|
+
applyBoneMap(clip, clipScene, boneMap);
|
|
252
|
+
}
|
|
253
|
+
if (mask != null) {
|
|
254
|
+
applyMask(clip, mask);
|
|
255
|
+
}
|
|
256
|
+
if (type != 'vrma') {
|
|
257
|
+
fixModelAnimationClip(model, clip, clipScene, removeXZMovement);
|
|
258
|
+
}
|
|
237
259
|
if (trimStartTime != null || trimEndTime != null) {
|
|
238
260
|
trimAnimationClip(clip, trimStartTime, trimEndTime);
|
|
239
261
|
}
|
package/dist/animation/mask.d.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { VRMHumanBoneName } from '@pixiv/three-vrm';
|
|
2
2
|
import type { AnimationClip } from 'three';
|
|
3
|
+
export type CharacterAnimationMask = (boneName: VRMHumanBoneName) => boolean;
|
|
3
4
|
export declare function applyMask(clip: AnimationClip, mask: CharacterAnimationMask): void;
|
|
5
|
+
export declare const upperBody: CharacterAnimationMask;
|
|
6
|
+
export declare const lowerBody: CharacterAnimationMask;
|
package/dist/animation/mask.js
CHANGED
|
@@ -1,3 +1,53 @@
|
|
|
1
1
|
export function applyMask(clip, mask) {
|
|
2
2
|
clip.tracks = clip.tracks.filter((track) => mask(track.name.split('.')[0]));
|
|
3
3
|
}
|
|
4
|
+
const upperBodyParts = [
|
|
5
|
+
'spine',
|
|
6
|
+
'chest',
|
|
7
|
+
'upperChest',
|
|
8
|
+
'neck',
|
|
9
|
+
'head',
|
|
10
|
+
'leftEye',
|
|
11
|
+
'rightEye',
|
|
12
|
+
'jaw',
|
|
13
|
+
'leftShoulder',
|
|
14
|
+
'leftUpperArm',
|
|
15
|
+
'leftLowerArm',
|
|
16
|
+
'leftHand',
|
|
17
|
+
'rightShoulder',
|
|
18
|
+
'rightUpperArm',
|
|
19
|
+
'rightLowerArm',
|
|
20
|
+
'rightHand',
|
|
21
|
+
'leftThumbMetacarpal',
|
|
22
|
+
'leftThumbProximal',
|
|
23
|
+
'leftThumbDistal',
|
|
24
|
+
'leftIndexProximal',
|
|
25
|
+
'leftIndexIntermediate',
|
|
26
|
+
'leftIndexDistal',
|
|
27
|
+
'leftMiddleProximal',
|
|
28
|
+
'leftMiddleIntermediate',
|
|
29
|
+
'leftMiddleDistal',
|
|
30
|
+
'leftRingProximal',
|
|
31
|
+
'leftRingIntermediate',
|
|
32
|
+
'leftRingDistal',
|
|
33
|
+
'leftLittleProximal',
|
|
34
|
+
'leftLittleIntermediate',
|
|
35
|
+
'leftLittleDistal',
|
|
36
|
+
'rightThumbMetacarpal',
|
|
37
|
+
'rightThumbProximal',
|
|
38
|
+
'rightThumbDistal',
|
|
39
|
+
'rightIndexProximal',
|
|
40
|
+
'rightIndexIntermediate',
|
|
41
|
+
'rightIndexDistal',
|
|
42
|
+
'rightMiddleProximal',
|
|
43
|
+
'rightMiddleIntermediate',
|
|
44
|
+
'rightMiddleDistal',
|
|
45
|
+
'rightRingProximal',
|
|
46
|
+
'rightRingIntermediate',
|
|
47
|
+
'rightRingDistal',
|
|
48
|
+
'rightLittleProximal',
|
|
49
|
+
'rightLittleIntermediate',
|
|
50
|
+
'rightLittleDistal',
|
|
51
|
+
];
|
|
52
|
+
export const upperBody = (name) => upperBodyParts.includes(name);
|
|
53
|
+
export const lowerBody = (name) => !upperBodyParts.includes(name);
|
package/dist/camera.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Object3D, Vector3, Vector3Tuple, Ray } from 'three';
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
export type SimpleCharacterCameraBehaviorOptions = {
|
|
2
|
+
export declare const FirstPersonCharacterCameraBehavior: CharacterCameraBehaviorOptions;
|
|
3
|
+
export type CharacterCameraBehaviorOptions = {
|
|
5
4
|
/**
|
|
6
5
|
* @default true
|
|
7
6
|
*/
|
|
@@ -61,6 +60,10 @@ export declare class CharacterCameraBehavior {
|
|
|
61
60
|
zoomDistance: number;
|
|
62
61
|
private collisionFreeZoomDistance;
|
|
63
62
|
private firstUpdate;
|
|
63
|
+
private readonly abortController;
|
|
64
|
+
private readonly yawReader;
|
|
65
|
+
private readonly pitchReader;
|
|
66
|
+
private readonly zoomReader;
|
|
64
67
|
private setRotationFromDelta;
|
|
65
68
|
private setDistanceFromDelta;
|
|
66
69
|
private computeCharacterBaseOffset;
|
|
@@ -70,5 +73,6 @@ export declare class CharacterCameraBehavior {
|
|
|
70
73
|
/**
|
|
71
74
|
* @param delta in seconds
|
|
72
75
|
*/
|
|
73
|
-
update(camera: Object3D, target: Object3D,
|
|
76
|
+
update(camera: Object3D, target: Object3D, deltaTime: number, raycast?: (ray: Ray, far: number) => number | undefined, options?: CharacterCameraBehaviorOptions): void;
|
|
77
|
+
dispose(): void;
|
|
74
78
|
}
|
package/dist/camera.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Vector3, Euler, Ray } from 'three';
|
|
1
|
+
import { Vector3, Euler, Ray, Quaternion } from 'three';
|
|
2
2
|
import { clamp } from 'three/src/math/MathUtils.js';
|
|
3
|
-
import {
|
|
3
|
+
import { RotatePitchAction, RotateYawAction, ZoomAction } from './action/index.js';
|
|
4
4
|
export const FirstPersonCharacterCameraBehavior = {
|
|
5
5
|
characterBaseOffset: [0, 1.6, 0],
|
|
6
6
|
zoom: { maxDistance: 0, minDistance: 0 },
|
|
@@ -11,6 +11,7 @@ const sphericalOffset = new Vector3();
|
|
|
11
11
|
const characterWorldPosition = new Vector3();
|
|
12
12
|
const euler = new Euler();
|
|
13
13
|
const rayHelper = new Ray();
|
|
14
|
+
const quaternionHelper = new Quaternion();
|
|
14
15
|
export class CharacterCameraBehavior {
|
|
15
16
|
rotationPitch = (-20 * Math.PI) / 180;
|
|
16
17
|
rotationYaw = 0;
|
|
@@ -18,6 +19,10 @@ export class CharacterCameraBehavior {
|
|
|
18
19
|
//internal state
|
|
19
20
|
collisionFreeZoomDistance = this.zoomDistance;
|
|
20
21
|
firstUpdate = true;
|
|
22
|
+
abortController = new AbortController();
|
|
23
|
+
yawReader = RotateYawAction.createReader(this.abortController.signal);
|
|
24
|
+
pitchReader = RotatePitchAction.createReader(this.abortController.signal);
|
|
25
|
+
zoomReader = ZoomAction.createReader(this.abortController.signal);
|
|
21
26
|
setRotationFromDelta(camera, delta, rotationOptions) {
|
|
22
27
|
if (delta.lengthSq() < 0.0001) {
|
|
23
28
|
// use current camera rotation if very close to target
|
|
@@ -55,7 +60,7 @@ export class CharacterCameraBehavior {
|
|
|
55
60
|
/**
|
|
56
61
|
* @param delta in seconds
|
|
57
62
|
*/
|
|
58
|
-
update(camera, target,
|
|
63
|
+
update(camera, target, deltaTime, raycast, options = true) {
|
|
59
64
|
if (options === false) {
|
|
60
65
|
this.firstUpdate = true;
|
|
61
66
|
return;
|
|
@@ -66,17 +71,21 @@ export class CharacterCameraBehavior {
|
|
|
66
71
|
}
|
|
67
72
|
//compute character->camera delta through offset
|
|
68
73
|
this.computeCharacterBaseOffset(chracterBaseOffsetHelper, options.characterBaseOffset);
|
|
74
|
+
target.getWorldQuaternion(quaternionHelper);
|
|
75
|
+
chracterBaseOffsetHelper.applyQuaternion(quaternionHelper);
|
|
69
76
|
target.getWorldPosition(characterWorldPosition);
|
|
70
77
|
characterWorldPosition.add(chracterBaseOffsetHelper);
|
|
71
78
|
camera.getWorldPosition(deltaHelper);
|
|
72
79
|
deltaHelper.sub(characterWorldPosition);
|
|
73
|
-
// apply rotation
|
|
80
|
+
// apply rotation actions to rotationYaw and rotationPitch if not disabled or first update
|
|
74
81
|
let rotationOptions = options.rotation ?? true;
|
|
75
82
|
if (!this.firstUpdate && rotationOptions !== false) {
|
|
76
83
|
rotationOptions = rotationOptions === true ? {} : rotationOptions;
|
|
77
84
|
const rotationSpeed = rotationOptions.speed ?? 1000.0;
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
this.yawReader.update();
|
|
86
|
+
this.pitchReader.update();
|
|
87
|
+
const deltaYaw = this.yawReader.get();
|
|
88
|
+
const deltaPitch = this.pitchReader.get();
|
|
80
89
|
this.rotationYaw = this.clampYaw(this.rotationYaw + deltaYaw * rotationSpeed * deltaTime, rotationOptions);
|
|
81
90
|
this.rotationPitch = this.clampPitch(this.rotationPitch + deltaPitch * rotationSpeed * deltaTime, rotationOptions);
|
|
82
91
|
}
|
|
@@ -87,12 +96,13 @@ export class CharacterCameraBehavior {
|
|
|
87
96
|
camera.rotation.set(this.rotationPitch, this.rotationYaw, 0, 'YXZ');
|
|
88
97
|
rayHelper.direction.set(0, 0, 1).applyEuler(camera.rotation);
|
|
89
98
|
rayHelper.origin.copy(characterWorldPosition);
|
|
90
|
-
// apply zoom
|
|
99
|
+
// apply zoom action to zoomDistance if not disabled or first update
|
|
91
100
|
let zoomOptions = options.zoom ?? true;
|
|
92
101
|
if (!this.firstUpdate && zoomOptions !== false) {
|
|
93
102
|
zoomOptions = zoomOptions === true ? {} : zoomOptions;
|
|
94
103
|
const zoomSpeed = zoomOptions.speed ?? 1000.0;
|
|
95
|
-
|
|
104
|
+
this.zoomReader.update();
|
|
105
|
+
const deltaZoom = this.zoomReader.get();
|
|
96
106
|
const zoomFactor = 1 + deltaZoom * zoomSpeed * deltaTime;
|
|
97
107
|
if (deltaZoom >= 0) {
|
|
98
108
|
this.zoomDistance *= zoomFactor;
|
|
@@ -120,10 +130,15 @@ export class CharacterCameraBehavior {
|
|
|
120
130
|
sphericalOffset.set(0, 0, this.collisionFreeZoomDistance);
|
|
121
131
|
sphericalOffset.applyEuler(camera.rotation);
|
|
122
132
|
// Get target position with offset (reuse helper vector)
|
|
123
|
-
target.getWorldPosition(characterWorldPosition);
|
|
124
133
|
this.computeCharacterBaseOffset(chracterBaseOffsetHelper, options.characterBaseOffset);
|
|
134
|
+
target.getWorldQuaternion(quaternionHelper);
|
|
135
|
+
chracterBaseOffsetHelper.applyQuaternion(quaternionHelper);
|
|
136
|
+
target.getWorldPosition(characterWorldPosition);
|
|
125
137
|
characterWorldPosition.add(chracterBaseOffsetHelper);
|
|
126
138
|
// Set camera position relative to target
|
|
127
139
|
camera.position.copy(characterWorldPosition).add(sphericalOffset);
|
|
128
140
|
}
|
|
141
|
+
dispose() {
|
|
142
|
+
this.abortController.abort();
|
|
143
|
+
}
|
|
129
144
|
}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/model/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { AnimationAction, AnimationMixer, Object3D, Quaternion } from 'three';
|
|
2
|
-
import { CharacterAnimationMask } from '../animation/index.js';
|
|
3
2
|
export { VRMHumanBoneName } from '@pixiv/three-vrm';
|
|
4
3
|
export * from './vrm.js';
|
|
5
4
|
export type CharacterModelOptions = {
|
|
@@ -23,7 +22,7 @@ export declare function flattenCharacterModelOptions(options: Exclude<CharacterM
|
|
|
23
22
|
export type CharacterModel = {
|
|
24
23
|
mixer: AnimationMixer;
|
|
25
24
|
scene: Object3D;
|
|
26
|
-
currentAnimations: Map<
|
|
25
|
+
currentAnimations: Map<string | undefined, AnimationAction>;
|
|
27
26
|
boneRotationOffset?: Quaternion;
|
|
28
27
|
};
|
|
29
28
|
export declare function loadCharacterModel(url?: string, type?: Exclude<CharacterModelOptions, boolean>['type'], boneRotationOffset?: Quaternion, castShadow?: boolean, receiveShadow?: boolean): Promise<CharacterModel>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { DefaultJumpKeys, DefaultMoveBackwardKeys, DefaultMoveForwardKeys, DefaultMoveLeftKeys, DefaultMoveRightKeys, DefaultRunKeys, KeyboardLocomotionActionBindings, } from '../action/keyboard.js';
|
|
2
|
+
import { PointerCaptureRotateZoomActionBindings } from '../action/pointer-capture.js';
|
|
3
|
+
import { PointerLockRotateZoomActionBindings } from '../action/pointer-lock.js';
|
|
4
|
+
import { ScreenJoystickLocomotionActionBindings } from '../action/screen-joystick.js';
|
|
5
|
+
export function applySimpleCharacterActionBindingOptions(actionBindingsList, options) {
|
|
6
|
+
for (const actionBindings of actionBindingsList) {
|
|
7
|
+
if (actionBindings instanceof ScreenJoystickLocomotionActionBindings) {
|
|
8
|
+
actionBindings.deadZonePx = options?.screenJoystickDeadZonePx;
|
|
9
|
+
actionBindings.runDistancePx = options?.screenJoystickRunDistancePx;
|
|
10
|
+
}
|
|
11
|
+
if (actionBindings instanceof PointerCaptureRotateZoomActionBindings) {
|
|
12
|
+
actionBindings.rotationSpeed = options?.pointerCaptureRotationSpeed;
|
|
13
|
+
actionBindings.zoomSpeed = options?.pointerCaptureZoomSpeed;
|
|
14
|
+
}
|
|
15
|
+
if (actionBindings instanceof PointerLockRotateZoomActionBindings) {
|
|
16
|
+
actionBindings.rotationSpeed = options?.pointerLockRotationSpeed;
|
|
17
|
+
actionBindings.zoomSpeed = options?.pointerLockZoomSpeed;
|
|
18
|
+
}
|
|
19
|
+
if (actionBindings instanceof KeyboardLocomotionActionBindings) {
|
|
20
|
+
actionBindings.moveForwardBinding.keys = options?.keyboardMoveForwardKeys ?? DefaultMoveForwardKeys;
|
|
21
|
+
actionBindings.moveBackwardBinding.requiresPointerLock = options?.keyboardRequiresPointerLock;
|
|
22
|
+
actionBindings.moveBackwardBinding.keys = options?.keyboardMoveBackwardKeys ?? DefaultMoveBackwardKeys;
|
|
23
|
+
actionBindings.moveBackwardBinding.requiresPointerLock = options?.keyboardRequiresPointerLock;
|
|
24
|
+
actionBindings.moveLeftBinding.keys = options?.keyboardMoveLeftKeys ?? DefaultMoveLeftKeys;
|
|
25
|
+
actionBindings.moveLeftBinding.requiresPointerLock = options?.keyboardRequiresPointerLock;
|
|
26
|
+
actionBindings.moveRightBinding.keys = options?.keyboardMoveRightKeys ?? DefaultMoveRightKeys;
|
|
27
|
+
actionBindings.moveRightBinding.requiresPointerLock = options?.keyboardRequiresPointerLock;
|
|
28
|
+
actionBindings.runBinding.keys = options?.keyboardRunKeys ?? DefaultRunKeys;
|
|
29
|
+
actionBindings.runBinding.requiresPointerLock = options?.keyboardRequiresPointerLock;
|
|
30
|
+
actionBindings.jumpBinding.keys = options?.keyboardJumpKeys ?? DefaultJumpKeys;
|
|
31
|
+
actionBindings.jumpBinding.requiresPointerLock = options?.keyboardRequiresPointerLock;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
import { Group, Object3D, Object3DEventMap, AnimationAction } from 'three';
|
|
2
2
|
import { CharacterAnimationOptions } from '../animation/index.js';
|
|
3
|
-
import { CharacterCameraBehavior,
|
|
4
|
-
import { Input, ScreenJoystickInputOptions, LocomotionKeyboardInputOptions, PointerCaptureInputOptions, PointerLockInputOptions, InputSystem } from '../input/index.js';
|
|
3
|
+
import { CharacterCameraBehavior, CharacterCameraBehaviorOptions } from '../camera.js';
|
|
5
4
|
import { CharacterModelOptions, CharacterModel } from '../model/index.js';
|
|
6
5
|
import { BvhCharacterPhysicsOptions, BvhCharacterPhysics, BvhPhysicsWorld } from '../physics/index.js';
|
|
7
6
|
export type SimpleCharacterState = {
|
|
8
7
|
camera: Object3D;
|
|
9
8
|
model?: CharacterModel;
|
|
10
9
|
physics: BvhCharacterPhysics;
|
|
11
|
-
inputSystem: InputSystem;
|
|
12
10
|
lastJump: number;
|
|
13
11
|
};
|
|
14
12
|
export type SimpleCharacterMovementOptions = {
|
|
@@ -53,7 +51,7 @@ export type SimpleCharacterAnimationOptions = {
|
|
|
53
51
|
/**
|
|
54
52
|
* @default "movement"
|
|
55
53
|
*/
|
|
56
|
-
|
|
54
|
+
yawRotationBasedOn?: 'camera' | 'movement';
|
|
57
55
|
/**
|
|
58
56
|
* @default 10
|
|
59
57
|
*/
|
|
@@ -63,16 +61,40 @@ export type SimpleCharacterAnimationOptions = {
|
|
|
63
61
|
*/
|
|
64
62
|
crossFadeDuration?: number;
|
|
65
63
|
};
|
|
66
|
-
export type
|
|
64
|
+
export type SimpleCharacterActionBindingOptions = {
|
|
65
|
+
screenJoystickRunDistancePx?: number;
|
|
66
|
+
screenJoystickDeadZonePx?: number;
|
|
67
|
+
pointerCaptureRotationSpeed?: number;
|
|
68
|
+
pointerCaptureZoomSpeed?: number;
|
|
69
|
+
pointerLockRotationSpeed?: number;
|
|
70
|
+
pointerLockZoomSpeed?: number;
|
|
71
|
+
keyboardRequiresPointerLock?: boolean;
|
|
72
|
+
keyboardMoveForwardKeys?: Array<string>;
|
|
73
|
+
keyboardMoveBackwardKeys?: Array<string>;
|
|
74
|
+
keyboardMoveLeftKeys?: Array<string>;
|
|
75
|
+
keyboardMoveRightKeys?: Array<string>;
|
|
76
|
+
keyboardRunKeys?: Array<string>;
|
|
77
|
+
keyboardJumpKeys?: Array<string>;
|
|
78
|
+
};
|
|
67
79
|
export type SimpleCharacterOptions = {
|
|
68
|
-
|
|
69
|
-
|
|
80
|
+
/**
|
|
81
|
+
* @deprecated use actionBindings instead
|
|
82
|
+
*/
|
|
83
|
+
readonly input?: ReadonlyArray<{
|
|
84
|
+
new (domElement: HTMLElement, abortSignal: AbortSignal): any;
|
|
70
85
|
}>;
|
|
71
|
-
|
|
86
|
+
readonly actionBindings?: ReadonlyArray<{
|
|
87
|
+
new (domElement: HTMLElement, abortSignal: AbortSignal): any;
|
|
88
|
+
}>;
|
|
89
|
+
/**
|
|
90
|
+
* @deprecated use actionBindingOptions instead
|
|
91
|
+
*/
|
|
92
|
+
inputOptions?: SimpleCharacterActionBindingOptions;
|
|
93
|
+
actionBindingOptions?: SimpleCharacterActionBindingOptions;
|
|
72
94
|
movement?: SimpleCharacterMovementOptions;
|
|
73
95
|
readonly model?: CharacterModelOptions | boolean;
|
|
74
96
|
physics?: BvhCharacterPhysicsOptions;
|
|
75
|
-
cameraBehavior?:
|
|
97
|
+
cameraBehavior?: CharacterCameraBehaviorOptions;
|
|
76
98
|
readonly animation?: SimpleCharacterAnimationOptions;
|
|
77
99
|
};
|
|
78
100
|
export declare class SimpleCharacter extends Group<Object3DEventMap & {
|
|
@@ -83,7 +105,6 @@ export declare class SimpleCharacter extends Group<Object3DEventMap & {
|
|
|
83
105
|
private readonly options;
|
|
84
106
|
readonly cameraBehavior: CharacterCameraBehavior;
|
|
85
107
|
readonly physics: BvhCharacterPhysics;
|
|
86
|
-
readonly inputSystem: InputSystem;
|
|
87
108
|
readonly currentAnimationRef: {
|
|
88
109
|
current?: AnimationAction;
|
|
89
110
|
};
|
|
@@ -92,6 +113,7 @@ export declare class SimpleCharacter extends Group<Object3DEventMap & {
|
|
|
92
113
|
private readonly graph;
|
|
93
114
|
private readonly abortController;
|
|
94
115
|
lastJump: number;
|
|
116
|
+
readonly abortSignal: AbortSignal;
|
|
95
117
|
constructor(camera: Object3D, world: BvhPhysicsWorld, domElement: HTMLElement, options?: SimpleCharacterOptions);
|
|
96
118
|
private init;
|
|
97
119
|
update(delta: number): void;
|
|
@@ -99,3 +121,4 @@ export declare class SimpleCharacter extends Group<Object3DEventMap & {
|
|
|
99
121
|
}
|
|
100
122
|
export * from './update-input-velocity.js';
|
|
101
123
|
export * from './update-rotation.js';
|
|
124
|
+
export * from './apply-input-options.js';
|