@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.
- 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/fbx.d.ts +4 -0
- package/dist/animation/{mixamo.js → fbx.js} +2 -3
- package/dist/animation/gltf.d.ts +2 -2
- package/dist/animation/gltf.js +2 -2
- package/dist/animation/index.d.ts +6 -3
- package/dist/animation/index.js +166 -56
- 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
- package/dist/animation/mixamo.d.ts +0 -3
|
@@ -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
|
|
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,
|
|
6
|
+
clipScene.animations.forEach((clip) => fixModelAnimationClip(model, clip, clipScene, removeXZMovement, boneMap));
|
|
8
7
|
return clipScene.animations;
|
|
9
8
|
}
|
package/dist/animation/gltf.d.ts
CHANGED
|
@@ -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>>;
|
package/dist/animation/gltf.js
CHANGED
|
@@ -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 './
|
|
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?: {
|
|
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>;
|
package/dist/animation/index.js
CHANGED
|
@@ -1,104 +1,211 @@
|
|
|
1
1
|
import { VRM } from '@pixiv/three-vrm';
|
|
2
2
|
import { Euler, Quaternion, QuaternionKeyframeTrack, Vector3, VectorKeyframeTrack, } from 'three';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
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
|
-
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const
|
|
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
|
|
15
|
-
if (
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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 [
|
|
32
|
-
const vrmBoneName = boneMap?.[
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
88
|
+
// Store rotations of rest-pose.
|
|
89
|
+
if (baseBone != null) {
|
|
90
|
+
baseThisLocalRestRotation_inverse.copy(baseBone.quaternion).invert();
|
|
42
91
|
}
|
|
43
92
|
else {
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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 === '
|
|
58
|
-
|
|
126
|
+
if (!(model instanceof VRM) && vrmBoneName === 'hips') {
|
|
127
|
+
targetThisLocalCurrentRotation.premultiply(nonVrmRotationOffset);
|
|
59
128
|
}
|
|
60
|
-
|
|
61
|
-
//TODO
|
|
62
|
-
}
|
|
63
|
-
quaternion.toArray(track.values, i);
|
|
129
|
+
targetThisLocalCurrentRotation.toArray(track.values, i);
|
|
64
130
|
}
|
|
65
|
-
track.name =
|
|
131
|
+
track.name = trackName;
|
|
66
132
|
}
|
|
67
133
|
else if (track instanceof VectorKeyframeTrack) {
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
|
|
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
|
-
|
|
78
|
-
|
|
156
|
+
position.negate();
|
|
157
|
+
position.y *= -1;
|
|
79
158
|
}
|
|
80
159
|
}
|
|
81
160
|
if (vrmBoneName === 'hips' && removeXZMovement) {
|
|
82
|
-
|
|
83
|
-
|
|
161
|
+
position.x = 0;
|
|
162
|
+
position.z = 0;
|
|
84
163
|
}
|
|
85
|
-
|
|
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 './
|
|
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
|
|
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
|
|
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;
|
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
|
},
|
|
@@ -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>>;
|