@pmndrs/viverse 0.1.18 → 0.1.20

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.
@@ -0,0 +1,24 @@
1
+ {
2
+ "Hips": "hips",
3
+ "Spine": "spine",
4
+ "Spine1": "chest",
5
+ "Spine2": "upperChest",
6
+ "Neck": "neck",
7
+ "Head": "head",
8
+ "LeftShoulder": "leftShoulder",
9
+ "LeftArm": "leftUpperArm",
10
+ "LeftForeArm": "leftLowerArm",
11
+ "LeftHand": "leftHand",
12
+ "RightShoulder": "rightShoulder",
13
+ "RightArm": "rightUpperArm",
14
+ "RightForeArm": "rightLowerArm",
15
+ "RightHand": "rightHand",
16
+ "LeftUpLeg": "leftUpperLeg",
17
+ "LeftLeg": "leftLowerLeg",
18
+ "LeftFoot": "leftFoot",
19
+ "LeftToe": "leftToes",
20
+ "RightUpLeg": "rightUpperLeg",
21
+ "RightLeg": "rightLowerLeg",
22
+ "RightFoot": "rightFoot",
23
+ "RightToe": "rightToes"
24
+ }
@@ -0,0 +1,4 @@
1
+ import { AnimationClip } from 'three';
2
+ import { loadCharacterModel } from '../model/index.js';
3
+ import type { VRMHumanBoneName } from '@pixiv/three-vrm';
4
+ export declare function loadVrmModelBvhAnimations(model: Exclude<Awaited<ReturnType<typeof loadCharacterModel>>, undefined>, url: string, removeXZMovement: boolean, boneMap?: Record<string, VRMHumanBoneName>): Promise<Array<AnimationClip>>;
@@ -0,0 +1,8 @@
1
+ import { BVHLoader } from 'three/examples/jsm/Addons.js';
2
+ import { bvhBoneMap, fixModelAnimationClip } from './index.js';
3
+ const loader = new BVHLoader();
4
+ export async function loadVrmModelBvhAnimations(model, url, removeXZMovement, boneMap) {
5
+ const clipScene = await loader.loadAsync(url);
6
+ fixModelAnimationClip(model, clipScene.clip, undefined, removeXZMovement, boneMap ?? bvhBoneMap);
7
+ return [clipScene.clip];
8
+ }
@@ -0,0 +1,4 @@
1
+ import { AnimationClip } from 'three';
2
+ import { loadCharacterModel } from '../model/index.js';
3
+ import type { VRMHumanBoneName } from '@pixiv/three-vrm';
4
+ export declare function loadVrmModelFbxAnimations(model: Exclude<Awaited<ReturnType<typeof loadCharacterModel>>, undefined>, url: string, removeXZMovement: boolean, boneMap?: Record<string, VRMHumanBoneName>): Promise<Array<AnimationClip>>;
@@ -1,9 +1,8 @@
1
1
  import { FBXLoader } from 'three/examples/jsm/Addons.js';
2
2
  import { fixModelAnimationClip } from './index.js';
3
- import mixamoBoneMap from './mixamo-bone-map.json';
4
3
  const loader = new FBXLoader();
5
- export async function loadVrmModelMixamoAnimations(model, url, removeXZMovement) {
4
+ export async function loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap) {
6
5
  const clipScene = await loader.loadAsync(url);
7
- clipScene.animations.forEach((clip) => fixModelAnimationClip(model, clip, clipScene, removeXZMovement, mixamoBoneMap));
6
+ clipScene.animations.forEach((clip) => fixModelAnimationClip(model, clip, clipScene, removeXZMovement, boneMap));
8
7
  return clipScene.animations;
9
8
  }
@@ -1,3 +1,3 @@
1
1
  import { AnimationClip } from 'three';
2
- import { loadCharacterModel } from '../model/index.js';
3
- export declare function loadVrmModelGltfAnimations(model: Exclude<Awaited<ReturnType<typeof loadCharacterModel>>, undefined>, url: string, removeXZMovement: boolean): Promise<Array<AnimationClip>>;
2
+ import { loadCharacterModel, VRMHumanBoneName } from '../model/index.js';
3
+ export declare function loadVrmModelGltfAnimations(model: Exclude<Awaited<ReturnType<typeof loadCharacterModel>>, undefined>, url: string, removeXZMovement: boolean, boneMap?: Record<string, VRMHumanBoneName>): Promise<Array<AnimationClip>>;
@@ -1,8 +1,8 @@
1
1
  import { GLTFLoader } from 'three/examples/jsm/Addons.js';
2
2
  import { fixModelAnimationClip } from './index.js';
3
3
  const loader = new GLTFLoader();
4
- export async function loadVrmModelGltfAnimations(model, url, removeXZMovement) {
4
+ export async function loadVrmModelGltfAnimations(model, url, removeXZMovement, boneMap) {
5
5
  const { animations, scene: clipScene } = await loader.loadAsync(url);
6
- animations.forEach((clip) => fixModelAnimationClip(model, clip, clipScene, removeXZMovement));
6
+ animations.forEach((clip) => fixModelAnimationClip(model, clip, clipScene, removeXZMovement, boneMap));
7
7
  return animations;
8
8
  }
@@ -1,19 +1,20 @@
1
1
  import { VRMHumanBoneName } from '@pixiv/three-vrm';
2
2
  import { AnimationClip, Object3D } from 'three';
3
3
  import { loadCharacterModel } from '../model/index.js';
4
- export declare function fixModelAnimationClip(model: Exclude<Awaited<ReturnType<typeof loadCharacterModel>>, undefined>, clip: AnimationClip, clipScene: Object3D, removeXZMovement: boolean, boneMap?: Record<string, VRMHumanBoneName>): void;
4
+ export declare function fixModelAnimationClip(model: Exclude<Awaited<ReturnType<typeof loadCharacterModel>>, undefined>, clip: AnimationClip, clipScene: Object3D | undefined, removeXZMovement: boolean, boneMap?: Record<string, VRMHumanBoneName>): void;
5
5
  export * from './gltf.js';
6
- export * from './mixamo.js';
6
+ export * from './fbx.js';
7
7
  export * from './vrma.js';
8
8
  export * from './utils.js';
9
9
  export type ModelAnimationOptions = {
10
- type: 'mixamo' | 'gltf' | 'vrma';
10
+ type?: 'mixamo' | 'gltf' | 'vrma' | 'fbx' | 'bvh';
11
11
  url: string;
12
12
  removeXZMovement?: boolean;
13
13
  trimTime?: {
14
14
  start?: number;
15
15
  end?: number;
16
16
  };
17
+ boneMap?: Record<string, VRMHumanBoneName>;
17
18
  scaleTime?: number;
18
19
  };
19
20
  export declare function loadCharacterModelAnimation(model: Exclude<Awaited<ReturnType<typeof loadCharacterModel>>, undefined>, options: ModelAnimationOptions): Promise<AnimationClip>;
@@ -28,3 +29,5 @@ declare const simpleCharacterAnimationUrls: {
28
29
  };
29
30
  export declare const simpleCharacterAnimationNames: Array<keyof typeof simpleCharacterAnimationUrls>;
30
31
  export declare function getSimpleCharacterModelAnimationOptions(animationName: keyof typeof simpleCharacterAnimationUrls): Promise<ModelAnimationOptions>;
32
+ export declare const mixamoBoneMap: Record<string, VRMHumanBoneName>;
33
+ export declare const bvhBoneMap: Record<string, VRMHumanBoneName>;
@@ -1,104 +1,211 @@
1
1
  import { VRM } from '@pixiv/three-vrm';
2
2
  import { Euler, Quaternion, QuaternionKeyframeTrack, Vector3, VectorKeyframeTrack, } from 'three';
3
- import { loadVrmModelGltfAnimations as loadModelGltfAnimations } from './gltf.js';
4
- import { loadVrmModelMixamoAnimations as loadModelMixamoAnimations } from './mixamo.js';
3
+ import _bvhBoneMap from './bvh-bone-map.json';
4
+ import { loadVrmModelBvhAnimations } from './bvh.js';
5
+ import { loadVrmModelFbxAnimations } from './fbx.js';
6
+ import { loadVrmModelGltfAnimations } from './gltf.js';
7
+ import _mixamoBoneMap from './mixamo-bone-map.json';
5
8
  import { scaleAnimationClipTime, trimAnimationClip } from './utils.js';
6
9
  import { loadVrmModelVrmaAnimations } from './vrma.js';
7
10
  import { cached } from '../utils.js';
8
- const restRotationInverse = new Quaternion();
9
- const parentRestWorldRotation = new Quaternion();
10
- const quaternion = new Quaternion();
11
- const vector = new Vector3();
11
+ //helper variables for the quaternion retargeting
12
+ const baseThisLocalRestRotation_inverse = new Quaternion();
13
+ const baseThisLocalCurrentRotation = new Quaternion();
14
+ const baseParentWorldRestRotation = new Quaternion();
15
+ const baseParentWorldRestRotation_inverse = new Quaternion();
16
+ const targetParentWorldRestRotation = new Quaternion();
17
+ const targetParentWorldRestRotation_inverse = new Quaternion();
18
+ const targetThisLocalRestRotation = new Quaternion();
19
+ const targetThisLocalCurrentRotation = new Quaternion();
20
+ //helper variables for the position retargeting
21
+ const position = new Vector3();
12
22
  const nonVrmRotationOffset = new Quaternion().setFromEuler(new Euler(0, Math.PI, 0));
23
+ //TODO: currently assumes the model is not yet transformed - loaded for the first time
13
24
  export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement, boneMap) {
14
- const hipsBoneName = boneMap == null ? 'hips' : Object.entries(boneMap).find(([, vrmBoneName]) => vrmBoneName === 'hips')?.[0];
15
- if (hipsBoneName == null) {
25
+ const hipsClipBoneName = boneMap == null ? 'hips' : Object.entries(boneMap).find(([, vrmBoneName]) => vrmBoneName === 'hips')?.[0];
26
+ if (hipsClipBoneName == null) {
16
27
  throw new Error('Failed to determine hips bone name for VRM animation. Please check the bone map or animation file.');
17
28
  }
18
- const clipSceneHips = clipScene.getObjectByName(hipsBoneName);
19
- const vrmHipsPosition = model instanceof VRM
20
- ? model.humanoid.normalizedRestPose.hips?.position
21
- : model.scene.getObjectByName('hips')?.position.toArray();
22
- if (clipSceneHips == null || vrmHipsPosition == null) {
23
- throw new Error('Failed to load VRM animation: missing animation hips object or VRM hips position.');
29
+ let restRoot;
30
+ let restRootParent;
31
+ if (!(model instanceof VRM)) {
32
+ restRoot = model.scene.getObjectByName('rest_root');
33
+ if (restRoot == null) {
34
+ throw new Error(`Model rest root not found.`);
35
+ }
36
+ restRootParent = restRoot?.parent;
37
+ restRoot.parent = null;
38
+ }
39
+ let positionScale = 1;
40
+ let clipSceneHips;
41
+ if (clipScene != null) {
42
+ clipSceneHips = clipScene.getObjectByName(hipsClipBoneName);
43
+ clipSceneHips?.parent?.updateMatrixWorld();
44
+ const vrmHipsPosition = model instanceof VRM
45
+ ? model.humanoid.normalizedRestPose.hips?.position
46
+ : 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.');
49
+ }
50
+ // Adjust with reference to hips height.
51
+ const motionHipsHeight = clipSceneHips.getWorldPosition(position).y;
52
+ const [_, vrmHipsHeight] = vrmHipsPosition;
53
+ positionScale = vrmHipsHeight / motionHipsHeight;
24
54
  }
25
- // Adjust with reference to hips height.
26
- const motionHipsHeight = clipSceneHips.position.y;
27
- const [_, vrmHipsHeight] = vrmHipsPosition;
28
- const hipsPositionScale = vrmHipsHeight / motionHipsHeight;
29
55
  for (const track of clip.tracks) {
30
56
  // Convert each tracks for VRM use, and push to `tracks`
31
- const [boneName, propertyName] = track.name.split('.');
32
- const vrmBoneName = boneMap?.[boneName] ?? boneName;
33
- const vrmNodeName = model instanceof VRM ? model.humanoid.getNormalizedBoneNode(vrmBoneName)?.name : vrmBoneName;
34
- const bone = clipScene.getObjectByName(boneName);
35
- if (vrmNodeName == null || bone == null) {
57
+ const [clipBoneName, propertyName] = track.name.split('.');
58
+ const vrmBoneName = (boneMap?.[clipBoneName] ?? clipBoneName);
59
+ const targetNormalizedBoneName = model instanceof VRM ? model.humanoid.getNormalizedBoneNode(vrmBoneName)?.name : vrmBoneName;
60
+ if (targetNormalizedBoneName == null) {
61
+ continue;
62
+ }
63
+ const trackName = `${targetNormalizedBoneName}.${propertyName}`;
64
+ let targetLocalBoneTransform;
65
+ let targetParentWorldBoneTransform;
66
+ //for vrm targetLocalBoneTransform and targetParentWorldBoneTransform are the identity quaternion
67
+ if (model instanceof VRM) {
68
+ targetLocalBoneTransform = { rotation: [0, 0, 0, 1] };
69
+ targetParentWorldBoneTransform = { rotation: [0, 0, 0, 1] };
70
+ }
71
+ else {
72
+ const targetBone = model.scene.getObjectByName(`rest_${vrmBoneName}`);
73
+ if (targetBone != null) {
74
+ targetLocalBoneTransform = { rotation: targetBone.quaternion.toArray() };
75
+ }
76
+ if (targetBone?.parent != null) {
77
+ targetParentWorldBoneTransform = { rotation: targetBone.parent.getWorldQuaternion(new Quaternion()).toArray() };
78
+ }
79
+ }
80
+ if (targetLocalBoneTransform == null) {
81
+ continue;
82
+ }
83
+ let baseBone = clipScene?.getObjectByName(clipBoneName);
84
+ if (clipScene != null && baseBone == null) {
36
85
  continue;
37
86
  }
38
87
  if (track instanceof QuaternionKeyframeTrack) {
39
- if (bone.parent != null) {
40
- bone.getWorldQuaternion(restRotationInverse).invert();
41
- bone.parent.getWorldQuaternion(parentRestWorldRotation);
88
+ // Store rotations of rest-pose.
89
+ if (baseBone != null) {
90
+ baseThisLocalRestRotation_inverse.copy(baseBone.quaternion).invert();
42
91
  }
43
92
  else {
44
- restRotationInverse.identity();
45
- parentRestWorldRotation.identity();
93
+ baseThisLocalRestRotation_inverse.identity();
94
+ }
95
+ if (baseBone?.parent != null) {
96
+ baseBone.parent.getWorldQuaternion(baseParentWorldRestRotation);
97
+ baseParentWorldRestRotation_inverse.copy(baseParentWorldRestRotation).invert();
98
+ }
99
+ else {
100
+ baseParentWorldRestRotation.identity();
101
+ baseParentWorldRestRotation_inverse.identity();
102
+ }
103
+ targetThisLocalRestRotation.fromArray(targetLocalBoneTransform.rotation ?? [0, 0, 0, 1]);
104
+ if (targetParentWorldBoneTransform != null) {
105
+ targetParentWorldRestRotation.fromArray(targetParentWorldBoneTransform.rotation ?? [0, 0, 0, 1]);
106
+ targetParentWorldRestRotation_inverse.copy(targetParentWorldRestRotation).invert();
107
+ }
108
+ else {
109
+ targetParentWorldRestRotation.identity();
110
+ targetParentWorldRestRotation_inverse.identity();
46
111
  }
47
- // Store rotations of rest-pose.
48
112
  for (let i = 0; i < track.values.length; i += 4) {
49
- quaternion.fromArray(track.values, i);
50
- if (model instanceof VRM) {
51
- quaternion.premultiply(parentRestWorldRotation).multiply(restRotationInverse);
52
- if (model.meta.metaVersion === '0') {
53
- quaternion.x *= -1;
54
- quaternion.z *= -1;
55
- }
113
+ baseThisLocalCurrentRotation.fromArray(track.values, i);
114
+ targetThisLocalCurrentRotation
115
+ .copy(targetParentWorldRestRotation_inverse)
116
+ .multiply(baseParentWorldRestRotation)
117
+ .multiply(baseThisLocalCurrentRotation)
118
+ .multiply(baseThisLocalRestRotation_inverse)
119
+ .multiply(baseParentWorldRestRotation_inverse)
120
+ .multiply(targetParentWorldRestRotation)
121
+ .multiply(targetThisLocalRestRotation);
122
+ if (model instanceof VRM && model.meta.metaVersion === '0') {
123
+ targetThisLocalCurrentRotation.x *= -1;
124
+ targetThisLocalCurrentRotation.z *= -1;
56
125
  }
57
- if (vrmBoneName === 'root') {
58
- quaternion.multiply(nonVrmRotationOffset);
126
+ if (!(model instanceof VRM) && vrmBoneName === 'hips') {
127
+ targetThisLocalCurrentRotation.premultiply(nonVrmRotationOffset);
59
128
  }
60
- if (removeXZMovement) {
61
- //TODO
62
- }
63
- quaternion.toArray(track.values, i);
129
+ targetThisLocalCurrentRotation.toArray(track.values, i);
64
130
  }
65
- track.name = `${vrmNodeName}.${propertyName}`;
131
+ track.name = trackName;
66
132
  }
67
133
  else if (track instanceof VectorKeyframeTrack) {
68
- track.name = `${vrmNodeName}.${propertyName}`;
69
- if (propertyName === 'scale') {
134
+ if (vrmBoneName != 'hips' && vrmBoneName != 'root') {
135
+ continue;
136
+ }
137
+ if (propertyName != 'position') {
70
138
  continue;
71
139
  }
72
140
  for (let i = 0; i < track.values.length; i += 3) {
73
- vector.fromArray(track.values, i);
74
- vector.multiplyScalar(hipsPositionScale);
141
+ position.fromArray(track.values, i);
142
+ if (clipSceneHips?.parent != null) {
143
+ if (vrmBoneName === 'hips') {
144
+ position.applyMatrix4(clipSceneHips.parent.matrixWorld);
145
+ }
146
+ else {
147
+ position.multiplyScalar(clipSceneHips.parent.matrixWorld.getMaxScaleOnAxis());
148
+ }
149
+ }
150
+ position.multiplyScalar(positionScale);
151
+ if (!(model instanceof VRM) && vrmBoneName === 'hips') {
152
+ position.applyQuaternion(nonVrmRotationOffset);
153
+ }
75
154
  if (model instanceof VRM) {
76
155
  if (model.meta.metaVersion === '0') {
77
- vector.negate();
78
- vector.y *= -1;
156
+ position.negate();
157
+ position.y *= -1;
79
158
  }
80
159
  }
81
160
  if (vrmBoneName === 'hips' && removeXZMovement) {
82
- vector.x = 0;
83
- vector.z = 0;
161
+ position.x = 0;
162
+ position.z = 0;
84
163
  }
85
- vector.toArray(track.values, i);
164
+ position.toArray(track.values, i);
86
165
  }
166
+ track.name = trackName;
87
167
  }
88
168
  }
169
+ if (restRoot != null && restRootParent != null) {
170
+ restRoot.parent = restRootParent;
171
+ }
89
172
  }
90
173
  export * from './gltf.js';
91
- export * from './mixamo.js';
174
+ export * from './fbx.js';
92
175
  export * from './vrma.js';
93
176
  export * from './utils.js';
94
- async function uncachedLoadModelAnimation(model, type, url, removeXZMovement, trimStartTime, trimEndTime, scaleTime) {
177
+ async function uncachedLoadModelAnimation(model, type, url, removeXZMovement, trimStartTime, trimEndTime, boneMap, scaleTime) {
95
178
  let clips;
179
+ if (type == null) {
180
+ const lowerCaseUrl = url.toLocaleLowerCase();
181
+ if (lowerCaseUrl.endsWith('.glb') || lowerCaseUrl.endsWith('.gltf')) {
182
+ type = 'gltf';
183
+ }
184
+ if (lowerCaseUrl.endsWith('.fbx')) {
185
+ type = 'fbx';
186
+ }
187
+ if (lowerCaseUrl.endsWith('.bvh')) {
188
+ type = 'bvh';
189
+ }
190
+ if (lowerCaseUrl.endsWith('.vrma')) {
191
+ type = 'vrma';
192
+ }
193
+ if (type == null) {
194
+ throw new Error(`Unable to infer animation type from url "${url}. Please specify the type of the animation manually."`);
195
+ }
196
+ }
96
197
  switch (type) {
97
198
  case 'gltf':
98
- clips = await loadModelGltfAnimations(model, url, removeXZMovement);
199
+ clips = await loadVrmModelGltfAnimations(model, url, removeXZMovement, boneMap);
200
+ break;
201
+ case 'fbx':
202
+ clips = await loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap);
203
+ break;
204
+ case 'bvh':
205
+ clips = await loadVrmModelBvhAnimations(model, url, removeXZMovement, boneMap ?? bvhBoneMap);
99
206
  break;
100
207
  case 'mixamo':
101
- clips = await loadModelMixamoAnimations(model, url, removeXZMovement);
208
+ clips = await loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap ?? mixamoBoneMap);
102
209
  break;
103
210
  case 'vrma':
104
211
  if (!(model instanceof VRM)) {
@@ -127,6 +234,7 @@ export function loadCharacterModelAnimation(model, options) {
127
234
  options.removeXZMovement ?? false,
128
235
  options.trimTime?.start,
129
236
  options.trimTime?.end,
237
+ options.boneMap,
130
238
  options.scaleTime,
131
239
  ]);
132
240
  }
@@ -152,3 +260,5 @@ export async function getSimpleCharacterModelAnimationOptions(animationName) {
152
260
  url: (await simpleCharacterAnimationUrls[animationName]()).url,
153
261
  };
154
262
  }
263
+ export const mixamoBoneMap = _mixamoBoneMap;
264
+ export const bvhBoneMap = _bvhBoneMap;
@@ -6,12 +6,23 @@ export { VRMHumanBoneName } from '@pixiv/three-vrm';
6
6
  export * from './vrm.js';
7
7
  async function uncachedLoadCharacterModel(type, url, boneRotationOffset, castShadow = true, receiveShadow = true) {
8
8
  let result;
9
- if (type == null || url == null) {
9
+ if (url == null) {
10
10
  //prepare loading the default model
11
11
  type = 'gltf';
12
12
  url = (await import('../assets/mannequin.js')).url;
13
13
  boneRotationOffset = new Quaternion().setFromEuler(new Euler(Math.PI, 0, Math.PI / 2, 'ZYX'));
14
14
  }
15
+ if (type == null) {
16
+ if (url.endsWith('.gltf') || url.endsWith('.glb')) {
17
+ type = 'gltf';
18
+ }
19
+ if (url.endsWith('.vrm')) {
20
+ type = 'vrm';
21
+ }
22
+ if (type == null) {
23
+ throw new Error(`Unable to infer model type from url "${url}. Please specify the type of the model manually."`);
24
+ }
25
+ }
15
26
  switch (type) {
16
27
  case 'vrm':
17
28
  result = await loadVrmCharacterModel(url);
@@ -30,6 +41,14 @@ async function uncachedLoadCharacterModel(type, url, boneRotationOffset, castSha
30
41
  obj.receiveShadow = true;
31
42
  }
32
43
  });
44
+ const rootBone = result.scene.getObjectByName('root');
45
+ if (rootBone == null) {
46
+ throw new Error(`unable to load model - missing root bone`);
47
+ }
48
+ const restPose = rootBone.clone();
49
+ restPose.visible = false;
50
+ restPose.traverse((bone) => (bone.name = `rest_${bone.name}`));
51
+ result.scene.add(restPose);
33
52
  return result;
34
53
  }
35
54
  function getCharacterModelDependencies(options = true) {
@@ -98,6 +98,7 @@ export declare class SimpleCharacter extends Group<Object3DEventMap & {
98
98
  actions?: Record<(typeof simpleCharacterAnimationNames)[number], AnimationAction> | undefined;
99
99
  model?: Awaited<Exclude<ReturnType<typeof loadCharacterModel>, undefined>>;
100
100
  private updateTimeline?;
101
+ private readonly abortController;
101
102
  constructor(camera: Object3D | (() => Object3D), world: BvhPhysicsWorld, domElement: HTMLElement, options?: SimpleCharacterOptions);
102
103
  getCamera(): Object3D<Object3DEventMap>;
103
104
  private init;
@@ -182,8 +182,8 @@ async function* SimpleCharacterTimeline(character) {
182
182
  init: () => {
183
183
  actions.jumpForward.paused = false;
184
184
  applyJumpForce();
185
+ return () => actions.jumpForward.fadeOut(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration);
185
186
  },
186
- cleanup: () => void actions.jumpForward.fadeOut(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration),
187
187
  until: animationFinished(actions.jumpForward),
188
188
  });
189
189
  if (character.physics.isGrounded) {
@@ -197,13 +197,13 @@ async function* SimpleCharacterTimeline(character) {
197
197
  init: () => {
198
198
  actions.jumpUp.paused = false;
199
199
  applyJumpForce();
200
+ return () => void actions.jumpUp.fadeOut(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration);
200
201
  },
201
- cleanup: () => void actions.jumpUp.fadeOut(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration),
202
202
  until: animationFinished(actions.jumpUp),
203
203
  }),
204
204
  transitionTo: {
205
205
  jumpDown: {
206
- when: (_, clock) => clock.actionTime > 0.3 && character.physics.isGrounded,
206
+ when: (_, _clock, actionTime) => actionTime > 0.3 && character.physics.isGrounded,
207
207
  },
208
208
  finally: 'jumpLoop',
209
209
  },
@@ -214,8 +214,8 @@ async function* SimpleCharacterTimeline(character) {
214
214
  actions.jumpLoop.reset();
215
215
  actions.jumpLoop.play();
216
216
  actions.jumpLoop.fadeIn(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration);
217
+ return () => actions.jumpLoop.fadeOut(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration);
217
218
  },
218
- cleanup: () => actions.jumpLoop.fadeOut(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration),
219
219
  until: forever(),
220
220
  }),
221
221
  transitionTo: {
@@ -230,8 +230,8 @@ async function* SimpleCharacterTimeline(character) {
230
230
  actions.jumpDown.reset();
231
231
  actions.jumpDown.play();
232
232
  actions.jumpDown.fadeIn(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration);
233
+ return () => actions.jumpDown.fadeOut(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration);
233
234
  },
234
- cleanup: () => actions.jumpDown.fadeOut(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration),
235
235
  until: timePassed(150, 'milliseconds'),
236
236
  }),
237
237
  transitionTo: { finally: 'moving' },
@@ -263,7 +263,7 @@ async function* SimpleCharacterTimeline(character) {
263
263
  nextAnimation.fadeIn(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration);
264
264
  currentAnimation = nextAnimation;
265
265
  },
266
- cleanup: () => currentAnimation?.fadeOut(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration),
266
+ init: () => () => currentAnimation?.fadeOut(character.options.animation?.crossFadeDuration ?? DefaultCrossFadeDuration),
267
267
  });
268
268
  },
269
269
  transitionTo: {
@@ -285,6 +285,7 @@ export class SimpleCharacter extends Group {
285
285
  actions;
286
286
  model;
287
287
  updateTimeline;
288
+ abortController = new AbortController();
288
289
  constructor(camera, world, domElement, options = {}) {
289
290
  super();
290
291
  this.camera = camera;
@@ -320,7 +321,7 @@ export class SimpleCharacter extends Group {
320
321
  this.actions.jumpForward.loop = LoopOnce;
321
322
  this.actions.jumpForward.clampWhenFinished = true;
322
323
  }
323
- this.updateTimeline = start(SimpleCharacterTimeline(this));
324
+ this.updateTimeline = start(SimpleCharacterTimeline(this), this.abortController.signal);
324
325
  this.dispatchEvent({ type: 'loaded' });
325
326
  }
326
327
  update(delta) {
@@ -333,6 +334,7 @@ export class SimpleCharacter extends Group {
333
334
  this.cameraBehavior.update(delta, this.options.cameraBehavior);
334
335
  }
335
336
  dispose() {
337
+ this.abortController.abort();
336
338
  this.parent?.remove(this);
337
339
  this.model?.scene.dispatchEvent({ type: 'dispose' });
338
340
  this.inputSystem.dispose();
package/package.json CHANGED
@@ -21,12 +21,12 @@
21
21
  "peerDependencies": {
22
22
  "three": "*"
23
23
  },
24
- "version": "0.1.18",
24
+ "version": "0.1.20",
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.1.15",
29
+ "@pmndrs/timeline": "^0.2.6",
30
30
  "@viverse/sdk": "1.2.10-alpha.0",
31
31
  "three-mesh-bvh": "^0.9.1"
32
32
  },
@@ -1,3 +0,0 @@
1
- import { AnimationClip } from 'three';
2
- import { loadCharacterModel } from '../model/index.js';
3
- export declare function loadVrmModelMixamoAnimations(model: Exclude<Awaited<ReturnType<typeof loadCharacterModel>>, undefined>, url: string, removeXZMovement: boolean): Promise<Array<AnimationClip>>;