@pmndrs/viverse 0.1.18 → 0.1.19
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/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 +4 -2
- package/dist/animation/index.js +83 -42
- package/package.json +1 -1
- package/dist/animation/mixamo.d.ts +0 -3
|
@@ -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
|
}
|
|
@@ -3,17 +3,18 @@ import { AnimationClip, Object3D } from 'three';
|
|
|
3
3
|
import { loadCharacterModel } from '../model/index.js';
|
|
4
4
|
export declare function fixModelAnimationClip(model: Exclude<Awaited<ReturnType<typeof loadCharacterModel>>, undefined>, clip: AnimationClip, clipScene: Object3D, 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: 'mixamo' | 'gltf' | 'vrma';
|
|
10
|
+
type: 'mixamo' | 'gltf' | 'vrma' | 'fbx';
|
|
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,4 @@ 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>;
|
package/dist/animation/index.js
CHANGED
|
@@ -1,21 +1,31 @@
|
|
|
1
1
|
import { VRM } from '@pixiv/three-vrm';
|
|
2
2
|
import { Euler, Quaternion, QuaternionKeyframeTrack, Vector3, VectorKeyframeTrack, } from 'three';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { loadVrmModelFbxAnimations } from './fbx.js';
|
|
4
|
+
import { loadVrmModelGltfAnimations } from './gltf.js';
|
|
5
5
|
import { scaleAnimationClipTime, trimAnimationClip } from './utils.js';
|
|
6
6
|
import { loadVrmModelVrmaAnimations } from './vrma.js';
|
|
7
7
|
import { cached } from '../utils.js';
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
const
|
|
8
|
+
import _mixamoBoneMap from './mixamo-bone-map.json';
|
|
9
|
+
//helper variables for the quaternion retargeting
|
|
10
|
+
const baseThisLocalRestRotation_inverse = new Quaternion();
|
|
11
|
+
const baseThisLocalCurrentRotation = new Quaternion();
|
|
12
|
+
const baseParentWorldRestRotation = new Quaternion();
|
|
13
|
+
const baseParentWorldRestRotation_inverse = new Quaternion();
|
|
14
|
+
const targetParentWorldRestRotation = new Quaternion();
|
|
15
|
+
const targetParentWorldRestRotation_inverse = new Quaternion();
|
|
16
|
+
const targetThisLocalRestRotation = new Quaternion();
|
|
17
|
+
const targetThisLocalCurrentRotation = new Quaternion();
|
|
18
|
+
//helper variables for the position retargeting
|
|
19
|
+
const position = new Vector3();
|
|
12
20
|
const nonVrmRotationOffset = new Quaternion().setFromEuler(new Euler(0, Math.PI, 0));
|
|
21
|
+
//TODO: currently assumes the model is not yet transformed - loaded for the first time
|
|
13
22
|
export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement, boneMap) {
|
|
14
23
|
const hipsBoneName = boneMap == null ? 'hips' : Object.entries(boneMap).find(([, vrmBoneName]) => vrmBoneName === 'hips')?.[0];
|
|
15
24
|
if (hipsBoneName == null) {
|
|
16
25
|
throw new Error('Failed to determine hips bone name for VRM animation. Please check the bone map or animation file.');
|
|
17
26
|
}
|
|
18
27
|
const clipSceneHips = clipScene.getObjectByName(hipsBoneName);
|
|
28
|
+
clipSceneHips?.parent?.updateMatrixWorld();
|
|
19
29
|
const vrmHipsPosition = model instanceof VRM
|
|
20
30
|
? model.humanoid.normalizedRestPose.hips?.position
|
|
21
31
|
: model.scene.getObjectByName('hips')?.position.toArray();
|
|
@@ -23,82 +33,111 @@ export function fixModelAnimationClip(model, clip, clipScene, removeXZMovement,
|
|
|
23
33
|
throw new Error('Failed to load VRM animation: missing animation hips object or VRM hips position.');
|
|
24
34
|
}
|
|
25
35
|
// Adjust with reference to hips height.
|
|
26
|
-
const motionHipsHeight = clipSceneHips.position.y;
|
|
36
|
+
const motionHipsHeight = clipSceneHips.getWorldPosition(position).y;
|
|
27
37
|
const [_, vrmHipsHeight] = vrmHipsPosition;
|
|
28
|
-
const
|
|
38
|
+
const positionScale = vrmHipsHeight / motionHipsHeight;
|
|
29
39
|
for (const track of clip.tracks) {
|
|
30
40
|
// Convert each tracks for VRM use, and push to `tracks`
|
|
31
41
|
const [boneName, propertyName] = track.name.split('.');
|
|
32
42
|
const vrmBoneName = boneMap?.[boneName] ?? boneName;
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
const targetBone = model instanceof VRM
|
|
44
|
+
? model.humanoid.getNormalizedBoneNode(vrmBoneName)
|
|
45
|
+
: model.scene.getObjectByName(vrmBoneName);
|
|
46
|
+
if (targetBone == null) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const vrmNodeName = model instanceof VRM ? targetBone.name : vrmBoneName;
|
|
50
|
+
const baseBone = clipScene.getObjectByName(boneName);
|
|
51
|
+
if (vrmNodeName == null || baseBone == null) {
|
|
36
52
|
continue;
|
|
37
53
|
}
|
|
38
54
|
if (track instanceof QuaternionKeyframeTrack) {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
55
|
+
// Store rotations of rest-pose.
|
|
56
|
+
baseThisLocalRestRotation_inverse.copy(baseBone.quaternion).invert();
|
|
57
|
+
if (baseBone.parent != null) {
|
|
58
|
+
baseBone.parent.getWorldQuaternion(baseParentWorldRestRotation);
|
|
59
|
+
baseParentWorldRestRotation_inverse.copy(baseParentWorldRestRotation).invert();
|
|
42
60
|
}
|
|
43
61
|
else {
|
|
44
|
-
|
|
45
|
-
|
|
62
|
+
baseParentWorldRestRotation.identity();
|
|
63
|
+
baseParentWorldRestRotation_inverse.identity();
|
|
64
|
+
}
|
|
65
|
+
targetThisLocalRestRotation.copy(targetBone.quaternion);
|
|
66
|
+
if (targetBone.parent != null) {
|
|
67
|
+
targetBone.parent.getWorldQuaternion(targetParentWorldRestRotation);
|
|
68
|
+
targetParentWorldRestRotation_inverse.copy(targetParentWorldRestRotation).invert();
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
targetParentWorldRestRotation.identity();
|
|
72
|
+
targetParentWorldRestRotation_inverse.identity();
|
|
46
73
|
}
|
|
47
|
-
// Store rotations of rest-pose.
|
|
48
74
|
for (let i = 0; i < track.values.length; i += 4) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
75
|
+
baseThisLocalCurrentRotation.fromArray(track.values, i);
|
|
76
|
+
targetThisLocalCurrentRotation
|
|
77
|
+
.copy(targetParentWorldRestRotation_inverse)
|
|
78
|
+
.multiply(baseParentWorldRestRotation)
|
|
79
|
+
.multiply(baseThisLocalCurrentRotation)
|
|
80
|
+
.multiply(baseThisLocalRestRotation_inverse)
|
|
81
|
+
.multiply(baseParentWorldRestRotation_inverse)
|
|
82
|
+
.multiply(targetParentWorldRestRotation)
|
|
83
|
+
.multiply(targetThisLocalRestRotation);
|
|
84
|
+
if (model instanceof VRM && model.meta.metaVersion === '0') {
|
|
85
|
+
targetThisLocalCurrentRotation.x *= -1;
|
|
86
|
+
targetThisLocalCurrentRotation.z *= -1;
|
|
56
87
|
}
|
|
57
|
-
if (vrmBoneName === '
|
|
58
|
-
|
|
88
|
+
if (!(model instanceof VRM) && vrmBoneName === 'hips') {
|
|
89
|
+
targetThisLocalCurrentRotation.premultiply(nonVrmRotationOffset);
|
|
59
90
|
}
|
|
60
|
-
|
|
61
|
-
//TODO
|
|
62
|
-
}
|
|
63
|
-
quaternion.toArray(track.values, i);
|
|
91
|
+
targetThisLocalCurrentRotation.toArray(track.values, i);
|
|
64
92
|
}
|
|
65
93
|
track.name = `${vrmNodeName}.${propertyName}`;
|
|
66
94
|
}
|
|
67
95
|
else if (track instanceof VectorKeyframeTrack) {
|
|
68
96
|
track.name = `${vrmNodeName}.${propertyName}`;
|
|
69
|
-
if (propertyName
|
|
97
|
+
if (propertyName != 'position') {
|
|
70
98
|
continue;
|
|
71
99
|
}
|
|
72
100
|
for (let i = 0; i < track.values.length; i += 3) {
|
|
73
|
-
|
|
74
|
-
|
|
101
|
+
position.fromArray(track.values, i);
|
|
102
|
+
if (clipSceneHips.parent != null) {
|
|
103
|
+
if (vrmBoneName === 'hips') {
|
|
104
|
+
position.applyMatrix4(clipSceneHips.parent.matrixWorld);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
position.multiplyScalar(clipSceneHips.parent.matrixWorld.getMaxScaleOnAxis());
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
position.multiplyScalar(positionScale);
|
|
75
111
|
if (model instanceof VRM) {
|
|
76
112
|
if (model.meta.metaVersion === '0') {
|
|
77
|
-
|
|
78
|
-
|
|
113
|
+
position.negate();
|
|
114
|
+
position.y *= -1;
|
|
79
115
|
}
|
|
80
116
|
}
|
|
81
117
|
if (vrmBoneName === 'hips' && removeXZMovement) {
|
|
82
|
-
|
|
83
|
-
|
|
118
|
+
position.x = 0;
|
|
119
|
+
position.z = 0;
|
|
84
120
|
}
|
|
85
|
-
|
|
121
|
+
position.toArray(track.values, i);
|
|
86
122
|
}
|
|
87
123
|
}
|
|
88
124
|
}
|
|
89
125
|
}
|
|
90
126
|
export * from './gltf.js';
|
|
91
|
-
export * from './
|
|
127
|
+
export * from './fbx.js';
|
|
92
128
|
export * from './vrma.js';
|
|
93
129
|
export * from './utils.js';
|
|
94
|
-
async function uncachedLoadModelAnimation(model, type, url, removeXZMovement, trimStartTime, trimEndTime, scaleTime) {
|
|
130
|
+
async function uncachedLoadModelAnimation(model, type, url, removeXZMovement, trimStartTime, trimEndTime, boneMap, scaleTime) {
|
|
95
131
|
let clips;
|
|
96
132
|
switch (type) {
|
|
97
133
|
case 'gltf':
|
|
98
|
-
clips = await
|
|
134
|
+
clips = await loadVrmModelGltfAnimations(model, url, removeXZMovement, boneMap);
|
|
135
|
+
break;
|
|
136
|
+
case 'fbx':
|
|
137
|
+
clips = await loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap);
|
|
99
138
|
break;
|
|
100
139
|
case 'mixamo':
|
|
101
|
-
clips = await
|
|
140
|
+
clips = await loadVrmModelFbxAnimations(model, url, removeXZMovement, boneMap ?? mixamoBoneMap);
|
|
102
141
|
break;
|
|
103
142
|
case 'vrma':
|
|
104
143
|
if (!(model instanceof VRM)) {
|
|
@@ -127,6 +166,7 @@ export function loadCharacterModelAnimation(model, options) {
|
|
|
127
166
|
options.removeXZMovement ?? false,
|
|
128
167
|
options.trimTime?.start,
|
|
129
168
|
options.trimTime?.end,
|
|
169
|
+
options.boneMap,
|
|
130
170
|
options.scaleTime,
|
|
131
171
|
]);
|
|
132
172
|
}
|
|
@@ -152,3 +192,4 @@ export async function getSimpleCharacterModelAnimationOptions(animationName) {
|
|
|
152
192
|
url: (await simpleCharacterAnimationUrls[animationName]()).url,
|
|
153
193
|
};
|
|
154
194
|
}
|
|
195
|
+
export const mixamoBoneMap = _mixamoBoneMap;
|
package/package.json
CHANGED
|
@@ -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>>;
|