@pmndrs/viverse 0.1.19 → 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.
- package/dist/animation/bvh-bone-map.json +24 -0
- package/dist/animation/bvh.d.ts +4 -0
- package/dist/animation/bvh.js +8 -0
- package/dist/animation/index.d.ts +3 -2
- package/dist/animation/index.js +100 -31
- package/dist/model/index.js +20 -1
- package/dist/simple-character.d.ts +1 -0
- package/dist/simple-character.js +9 -7
- package/package.json +2 -2
|
@@ -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
|
+
}
|
|
@@ -1,13 +1,13 @@
|
|
|
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
6
|
export * from './fbx.js';
|
|
7
7
|
export * from './vrma.js';
|
|
8
8
|
export * from './utils.js';
|
|
9
9
|
export type ModelAnimationOptions = {
|
|
10
|
-
type
|
|
10
|
+
type?: 'mixamo' | 'gltf' | 'vrma' | 'fbx' | 'bvh';
|
|
11
11
|
url: string;
|
|
12
12
|
removeXZMovement?: boolean;
|
|
13
13
|
trimTime?: {
|
|
@@ -30,3 +30,4 @@ declare const simpleCharacterAnimationUrls: {
|
|
|
30
30
|
export declare const simpleCharacterAnimationNames: Array<keyof typeof simpleCharacterAnimationUrls>;
|
|
31
31
|
export declare function getSimpleCharacterModelAnimationOptions(animationName: keyof typeof simpleCharacterAnimationUrls): Promise<ModelAnimationOptions>;
|
|
32
32
|
export declare const mixamoBoneMap: Record<string, VRMHumanBoneName>;
|
|
33
|
+
export declare const bvhBoneMap: Record<string, VRMHumanBoneName>;
|
package/dist/animation/index.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { VRM } from '@pixiv/three-vrm';
|
|
2
2
|
import { Euler, Quaternion, QuaternionKeyframeTrack, Vector3, VectorKeyframeTrack, } from 'three';
|
|
3
|
+
import _bvhBoneMap from './bvh-bone-map.json';
|
|
4
|
+
import { loadVrmModelBvhAnimations } from './bvh.js';
|
|
3
5
|
import { loadVrmModelFbxAnimations } from './fbx.js';
|
|
4
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
|
-
import _mixamoBoneMap from './mixamo-bone-map.json';
|
|
9
11
|
//helper variables for the quaternion retargeting
|
|
10
12
|
const baseThisLocalRestRotation_inverse = new Quaternion();
|
|
11
13
|
const baseThisLocalCurrentRotation = new Quaternion();
|
|
@@ -20,41 +22,77 @@ const position = new Vector3();
|
|
|
20
22
|
const nonVrmRotationOffset = new Quaternion().setFromEuler(new Euler(0, Math.PI, 0));
|
|
21
23
|
//TODO: currently assumes the model is not yet transformed - loaded for the first time
|
|
22
24
|
export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement, boneMap) {
|
|
23
|
-
const
|
|
24
|
-
if (
|
|
25
|
+
const hipsClipBoneName = boneMap == null ? 'hips' : Object.entries(boneMap).find(([, vrmBoneName]) => vrmBoneName === 'hips')?.[0];
|
|
26
|
+
if (hipsClipBoneName == null) {
|
|
25
27
|
throw new Error('Failed to determine hips bone name for VRM animation. Please check the bone map or animation file.');
|
|
26
28
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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;
|
|
34
54
|
}
|
|
35
|
-
// Adjust with reference to hips height.
|
|
36
|
-
const motionHipsHeight = clipSceneHips.getWorldPosition(position).y;
|
|
37
|
-
const [_, vrmHipsHeight] = vrmHipsPosition;
|
|
38
|
-
const positionScale = vrmHipsHeight / motionHipsHeight;
|
|
39
55
|
for (const track of clip.tracks) {
|
|
40
56
|
// Convert each tracks for VRM use, and push to `tracks`
|
|
41
|
-
const [
|
|
42
|
-
const vrmBoneName = boneMap?.[
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
: model.scene.getObjectByName(vrmBoneName);
|
|
46
|
-
if (targetBone == 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) {
|
|
47
61
|
continue;
|
|
48
62
|
}
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
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) {
|
|
52
85
|
continue;
|
|
53
86
|
}
|
|
54
87
|
if (track instanceof QuaternionKeyframeTrack) {
|
|
55
88
|
// Store rotations of rest-pose.
|
|
56
|
-
|
|
57
|
-
|
|
89
|
+
if (baseBone != null) {
|
|
90
|
+
baseThisLocalRestRotation_inverse.copy(baseBone.quaternion).invert();
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
baseThisLocalRestRotation_inverse.identity();
|
|
94
|
+
}
|
|
95
|
+
if (baseBone?.parent != null) {
|
|
58
96
|
baseBone.parent.getWorldQuaternion(baseParentWorldRestRotation);
|
|
59
97
|
baseParentWorldRestRotation_inverse.copy(baseParentWorldRestRotation).invert();
|
|
60
98
|
}
|
|
@@ -62,9 +100,9 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
62
100
|
baseParentWorldRestRotation.identity();
|
|
63
101
|
baseParentWorldRestRotation_inverse.identity();
|
|
64
102
|
}
|
|
65
|
-
targetThisLocalRestRotation.
|
|
66
|
-
if (
|
|
67
|
-
|
|
103
|
+
targetThisLocalRestRotation.fromArray(targetLocalBoneTransform.rotation ?? [0, 0, 0, 1]);
|
|
104
|
+
if (targetParentWorldBoneTransform != null) {
|
|
105
|
+
targetParentWorldRestRotation.fromArray(targetParentWorldBoneTransform.rotation ?? [0, 0, 0, 1]);
|
|
68
106
|
targetParentWorldRestRotation_inverse.copy(targetParentWorldRestRotation).invert();
|
|
69
107
|
}
|
|
70
108
|
else {
|
|
@@ -90,16 +128,18 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
90
128
|
}
|
|
91
129
|
targetThisLocalCurrentRotation.toArray(track.values, i);
|
|
92
130
|
}
|
|
93
|
-
track.name =
|
|
131
|
+
track.name = trackName;
|
|
94
132
|
}
|
|
95
133
|
else if (track instanceof VectorKeyframeTrack) {
|
|
96
|
-
|
|
134
|
+
if (vrmBoneName != 'hips' && vrmBoneName != 'root') {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
97
137
|
if (propertyName != 'position') {
|
|
98
138
|
continue;
|
|
99
139
|
}
|
|
100
140
|
for (let i = 0; i < track.values.length; i += 3) {
|
|
101
141
|
position.fromArray(track.values, i);
|
|
102
|
-
if (clipSceneHips
|
|
142
|
+
if (clipSceneHips?.parent != null) {
|
|
103
143
|
if (vrmBoneName === 'hips') {
|
|
104
144
|
position.applyMatrix4(clipSceneHips.parent.matrixWorld);
|
|
105
145
|
}
|
|
@@ -108,6 +148,9 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
108
148
|
}
|
|
109
149
|
}
|
|
110
150
|
position.multiplyScalar(positionScale);
|
|
151
|
+
if (!(model instanceof VRM) && vrmBoneName === 'hips') {
|
|
152
|
+
position.applyQuaternion(nonVrmRotationOffset);
|
|
153
|
+
}
|
|
111
154
|
if (model instanceof VRM) {
|
|
112
155
|
if (model.meta.metaVersion === '0') {
|
|
113
156
|
position.negate();
|
|
@@ -120,8 +163,12 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
120
163
|
}
|
|
121
164
|
position.toArray(track.values, i);
|
|
122
165
|
}
|
|
166
|
+
track.name = trackName;
|
|
123
167
|
}
|
|
124
168
|
}
|
|
169
|
+
if (restRoot != null && restRootParent != null) {
|
|
170
|
+
restRoot.parent = restRootParent;
|
|
171
|
+
}
|
|
125
172
|
}
|
|
126
173
|
export * from './gltf.js';
|
|
127
174
|
export * from './fbx.js';
|
|
@@ -129,6 +176,24 @@ export * from './vrma.js';
|
|
|
129
176
|
export * from './utils.js';
|
|
130
177
|
async function uncachedLoadModelAnimation(model, type, url, removeXZMovement, trimStartTime, trimEndTime, boneMap, scaleTime) {
|
|
131
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
|
+
}
|
|
132
197
|
switch (type) {
|
|
133
198
|
case 'gltf':
|
|
134
199
|
clips = await loadVrmModelGltfAnimations(model, url, removeXZMovement, boneMap);
|
|
@@ -136,6 +201,9 @@ async function uncachedLoadModelAnimation(model, type, url, removeXZMovement, tr
|
|
|
136
201
|
case 'fbx':
|
|
137
202
|
clips = await loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap);
|
|
138
203
|
break;
|
|
204
|
+
case 'bvh':
|
|
205
|
+
clips = await loadVrmModelBvhAnimations(model, url, removeXZMovement, boneMap ?? bvhBoneMap);
|
|
206
|
+
break;
|
|
139
207
|
case 'mixamo':
|
|
140
208
|
clips = await loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap ?? mixamoBoneMap);
|
|
141
209
|
break;
|
|
@@ -193,3 +261,4 @@ export async function getSimpleCharacterModelAnimationOptions(animationName) {
|
|
|
193
261
|
};
|
|
194
262
|
}
|
|
195
263
|
export const mixamoBoneMap = _mixamoBoneMap;
|
|
264
|
+
export const bvhBoneMap = _bvhBoneMap;
|
package/dist/model/index.js
CHANGED
|
@@ -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 (
|
|
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;
|
package/dist/simple-character.js
CHANGED
|
@@ -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: (_,
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
},
|