@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.
@@ -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
  }
@@ -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 './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';
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>;
@@ -1,21 +1,31 @@
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 { 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
- const restRotationInverse = new Quaternion();
9
- const parentRestWorldRotation = new Quaternion();
10
- const quaternion = new Quaternion();
11
- const vector = new Vector3();
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 hipsPositionScale = vrmHipsHeight / motionHipsHeight;
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 vrmNodeName = model instanceof VRM ? model.humanoid.getNormalizedBoneNode(vrmBoneName)?.name : vrmBoneName;
34
- const bone = clipScene.getObjectByName(boneName);
35
- if (vrmNodeName == null || bone == null) {
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
- if (bone.parent != null) {
40
- bone.getWorldQuaternion(restRotationInverse).invert();
41
- bone.parent.getWorldQuaternion(parentRestWorldRotation);
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
- restRotationInverse.identity();
45
- parentRestWorldRotation.identity();
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
- 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
- }
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 === 'root') {
58
- quaternion.multiply(nonVrmRotationOffset);
88
+ if (!(model instanceof VRM) && vrmBoneName === 'hips') {
89
+ targetThisLocalCurrentRotation.premultiply(nonVrmRotationOffset);
59
90
  }
60
- if (removeXZMovement) {
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 === 'scale') {
97
+ if (propertyName != 'position') {
70
98
  continue;
71
99
  }
72
100
  for (let i = 0; i < track.values.length; i += 3) {
73
- vector.fromArray(track.values, i);
74
- vector.multiplyScalar(hipsPositionScale);
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
- vector.negate();
78
- vector.y *= -1;
113
+ position.negate();
114
+ position.y *= -1;
79
115
  }
80
116
  }
81
117
  if (vrmBoneName === 'hips' && removeXZMovement) {
82
- vector.x = 0;
83
- vector.z = 0;
118
+ position.x = 0;
119
+ position.z = 0;
84
120
  }
85
- vector.toArray(track.values, i);
121
+ position.toArray(track.values, i);
86
122
  }
87
123
  }
88
124
  }
89
125
  }
90
126
  export * from './gltf.js';
91
- export * from './mixamo.js';
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 loadModelGltfAnimations(model, url, removeXZMovement);
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 loadModelMixamoAnimations(model, url, removeXZMovement);
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
@@ -21,7 +21,7 @@
21
21
  "peerDependencies": {
22
22
  "three": "*"
23
23
  },
24
- "version": "0.1.18",
24
+ "version": "0.1.19",
25
25
  "type": "module",
26
26
  "dependencies": {
27
27
  "@pixiv/three-vrm": "^3.4.2",
@@ -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>>;