@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.
Files changed (64) hide show
  1. package/dist/action/action.d.ts +40 -0
  2. package/dist/action/action.js +114 -0
  3. package/dist/action/index.d.ts +18 -0
  4. package/dist/action/index.js +25 -0
  5. package/dist/action/keyboard.d.ts +26 -0
  6. package/dist/action/keyboard.js +89 -0
  7. package/dist/action/pointer-capture.d.ts +8 -0
  8. package/dist/{input → action}/pointer-capture.js +17 -42
  9. package/dist/action/pointer-lock.d.ts +6 -0
  10. package/dist/action/pointer-lock.js +49 -0
  11. package/dist/action/pointer.d.ts +18 -0
  12. package/dist/action/pointer.js +90 -0
  13. package/dist/action/screen-joystick.d.ts +16 -0
  14. package/dist/action/screen-joystick.js +120 -0
  15. package/dist/action/screen-jump-button.d.ts +6 -0
  16. package/dist/action/screen-jump-button.js +32 -0
  17. package/dist/animation/bone-map.d.ts +4 -0
  18. package/dist/animation/bone-map.js +11 -0
  19. package/dist/animation/default.d.ts +9 -1
  20. package/dist/animation/default.js +16 -9
  21. package/dist/animation/index.d.ts +8 -13
  22. package/dist/animation/index.js +61 -39
  23. package/dist/animation/mask.d.ts +4 -1
  24. package/dist/animation/mask.js +50 -0
  25. package/dist/camera.d.ts +8 -4
  26. package/dist/camera.js +24 -9
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +1 -1
  29. package/dist/model/index.d.ts +1 -2
  30. package/dist/simple-character/apply-input-options.d.ts +2 -0
  31. package/dist/simple-character/apply-input-options.js +34 -0
  32. package/dist/simple-character/index.d.ts +33 -10
  33. package/dist/simple-character/index.js +18 -19
  34. package/dist/simple-character/state/jump-down.js +2 -1
  35. package/dist/simple-character/state/jump-forward.js +4 -2
  36. package/dist/simple-character/state/jump-loop.js +2 -1
  37. package/dist/simple-character/state/jump-start.js +2 -2
  38. package/dist/simple-character/state/jump-up.js +4 -2
  39. package/dist/simple-character/state/movement.js +12 -8
  40. package/dist/simple-character/update-input-velocity.d.ts +1 -2
  41. package/dist/simple-character/update-input-velocity.js +4 -4
  42. package/dist/simple-character/update-rotation.js +1 -1
  43. package/dist/utils.d.ts +5 -5
  44. package/dist/utils.js +15 -6
  45. package/package.json +2 -2
  46. package/dist/animation/bvh.d.ts +0 -4
  47. package/dist/animation/bvh.js +0 -8
  48. package/dist/animation/fbx.d.ts +0 -4
  49. package/dist/animation/fbx.js +0 -8
  50. package/dist/animation/gltf.d.ts +0 -3
  51. package/dist/animation/gltf.js +0 -8
  52. package/dist/animation/vrma.d.ts +0 -3
  53. package/dist/animation/vrma.js +0 -8
  54. package/dist/input/index.d.ts +0 -31
  55. package/dist/input/index.js +0 -77
  56. package/dist/input/keyboard.d.ts +0 -16
  57. package/dist/input/keyboard.js +0 -82
  58. package/dist/input/pointer-capture.d.ts +0 -20
  59. package/dist/input/pointer-lock.d.ts +0 -17
  60. package/dist/input/pointer-lock.js +0 -55
  61. package/dist/input/screen-joystick.d.ts +0 -18
  62. package/dist/input/screen-joystick.js +0 -120
  63. package/dist/input/screen-jump-button.d.ts +0 -8
  64. package/dist/input/screen-jump-button.js +0 -49
@@ -1,18 +1,25 @@
1
- export async function loadDefaultCharacterAnimationUrl(type) {
2
- switch (type) {
3
- case 'idle':
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 'jumpDown':
12
+ case JumpDownAnimationUrl:
6
13
  return (await import('../assets/jump-down.js')).url;
7
- case 'jumpForward':
14
+ case JumpForwardAnimationUrl:
8
15
  return (await import('../assets/jump-forward.js')).url;
9
- case 'jumpLoop':
16
+ case JumpLoopAnimationUrl:
10
17
  return (await import('../assets/jump-loop.js')).url;
11
- case 'jumpUp':
18
+ case JumpUpAnimationUrl:
12
19
  return (await import('../assets/jump-up.js')).url;
13
- case 'run':
20
+ case RunAnimationUrl:
14
21
  return (await import('../assets/run.js')).url;
15
- case 'walk':
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 { loadDefaultCharacterAnimationUrl } from './default.js';
4
- import type { CharacterModel } from '../model/index.js';
5
- export declare function fixModelAnimationClip(model: CharacterModel, clip: AnimationClip, clipScene: Object3D | undefined, removeXZMovement: boolean, boneMap?: Record<string, VRMHumanBoneName>): void;
6
- export * from './gltf.js';
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 type CharacterAnimationMask = (boneName: VRMHumanBoneName) => boolean;
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>;
@@ -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 { loadVrmModelBvhAnimations } from './bvh.js';
5
- import { loadDefaultCharacterAnimationUrl } from './default.js';
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 { loadVrmModelVrmaAnimations } from './vrma.js';
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, boneMap) {
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(hipsClipBoneName);
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 == null || vrmHipsPosition == null) {
48
- throw new Error('Failed to load animation: missing animation hips object or VRM hips position.');
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 vrmBoneName = (boneMap?.[clipBoneName] ?? clipBoneName);
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_${vrmBoneName}`);
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) && vrmBoneName === 'hips') {
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 (vrmBoneName != 'hips' && vrmBoneName != 'root') {
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 (vrmBoneName === 'hips') {
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) && vrmBoneName === 'hips') {
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 (vrmBoneName === 'hips' && removeXZMovement) {
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 === 'object') {
191
- url = await loadDefaultCharacterAnimationUrl(url.default);
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
- clips = await loadVrmModelGltfAnimations(model, url, removeXZMovement, boneMap);
214
+ case 'gltf': {
215
+ const { animations, scene } = await gltfLoader.loadAsync(url);
216
+ clips = animations;
217
+ clipScene = scene;
216
218
  break;
217
- case 'fbx':
218
- clips = await loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap);
219
+ }
220
+ case 'fbx': {
221
+ const scene = await fbxLoader.loadAsync(url);
222
+ clips = scene.animations;
223
+ clipScene = scene;
219
224
  break;
220
- case 'bvh':
221
- clips = await loadVrmModelBvhAnimations(model, url, removeXZMovement, boneMap ?? bvhBoneMap);
225
+ }
226
+ case 'bvh': {
227
+ const { clip, skeleton } = await bvhLoader.loadAsync(url);
228
+ clips = [clip];
229
+ boneMap ??= bvhBoneMap;
222
230
  break;
223
- case 'mixamo':
224
- clips = await loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap ?? mixamoBoneMap);
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 loadVrmModelVrmaAnimations(model, url, removeXZMovement);
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
  }
@@ -1,3 +1,6 @@
1
- import type { CharacterAnimationMask } from './index.js';
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;
@@ -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
- import { InputSystem } from './input/index.js';
3
- export declare const FirstPersonCharacterCameraBehavior: SimpleCharacterCameraBehaviorOptions;
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, inputSystem: InputSystem, deltaTime: number, raycast?: (ray: Ray, far: number) => number | undefined, options?: SimpleCharacterCameraBehaviorOptions): void;
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 { DeltaYawField, DeltaPitchField, DeltaZoomField } from './input/index.js';
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, inputSystem, deltaTime, raycast, options = true) {
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 input to rotationYaw and rotationPitch if not disabled or first update
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
- const deltaYaw = inputSystem.get(DeltaYawField);
79
- const deltaPitch = inputSystem.get(DeltaPitchField);
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 input to zoomDistance if not disabled or first update
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
- const deltaZoom = inputSystem.get(DeltaZoomField);
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
@@ -1,5 +1,5 @@
1
1
  export * from './utils.js';
2
- export * from './input/index.js';
2
+ export * from './action/index.js';
3
3
  export * from './utils.js';
4
4
  export * from './camera.js';
5
5
  export * from './physics/index.js';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from './utils.js';
2
- export * from './input/index.js';
2
+ export * from './action/index.js';
3
3
  export * from './utils.js';
4
4
  export * from './camera.js';
5
5
  export * from './physics/index.js';
@@ -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<CharacterAnimationMask | undefined, AnimationAction>;
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,2 @@
1
+ import { SimpleCharacterActionBindingOptions } from './index.js';
2
+ export declare function applySimpleCharacterActionBindingOptions(actionBindingsList: Array<unknown>, options?: SimpleCharacterActionBindingOptions): void;
@@ -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, SimpleCharacterCameraBehaviorOptions } from '../camera.js';
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
- yawRotationBasdOn?: 'camera' | 'movement';
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 SimpleCharacterInputOptions = ScreenJoystickInputOptions & PointerCaptureInputOptions & PointerLockInputOptions & LocomotionKeyboardInputOptions;
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
- readonly input?: ReadonlyArray<Input | {
69
- new (domElement: HTMLElement): Input;
80
+ /**
81
+ * @deprecated use actionBindings instead
82
+ */
83
+ readonly input?: ReadonlyArray<{
84
+ new (domElement: HTMLElement, abortSignal: AbortSignal): any;
70
85
  }>;
71
- inputOptions?: SimpleCharacterInputOptions;
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?: SimpleCharacterCameraBehaviorOptions;
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';